Skip to content

Commit

Permalink
Refactor OS exit code to be EXIT_SUCCESS by default
Browse files Browse the repository at this point in the history
- `Main::setup` early exits (failure or `--help`/`--version`) now
  consistently return `EXIT_FAILURE` or `EXIT_SUCCESS` on all platforms,
  instead of 255 on some and a Godot Error code on others.
- `Main::start` now returns the exit code, simplifying the handling of early
  failures.
- `Main::iteration` needs to explicit set the exit code in OS if it errors
  out.
- Web and iOS now properly return `OS::get_exit_code()` instead of 0.
  • Loading branch information
akien-mga authored and dreed-sd committed Apr 18, 2024
1 parent 0c7f86f commit 3dd6485
Show file tree
Hide file tree
Showing 8 changed files with 83 additions and 84 deletions.
3 changes: 2 additions & 1 deletion core/os/os.h
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,8 @@ class OS {
bool _verbose_stdout = false;
bool _debug_stdout = false;
String _local_clipboard;
int _exit_code = EXIT_FAILURE; // unexpected exit is marked as failure
// Assume success by default, all failure cases need to set EXIT_FAILURE explicitly.
int _exit_code = EXIT_SUCCESS;
bool _allow_hidpi = false;
bool _allow_layered = false;
bool _stdout_enabled = true;
Expand Down
96 changes: 43 additions & 53 deletions main/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -648,6 +648,7 @@ Error Main::test_setup() {

return OK;
}

// The order is the same as in `Main::cleanup()`.
void Main::test_cleanup() {
ERR_FAIL_COND(!_start_success);
Expand Down Expand Up @@ -858,8 +859,9 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph
packed_data->add_pack_source(zip_packed_data);
#endif

// Default exit code, can be modified for certain errors.
Error exit_code = ERR_INVALID_PARAMETER;
// Exit error code used in the `goto error` conditions.
// It's returned as the program exit code. ERR_HELP is special cased and handled as success (0).
Error exit_err = ERR_INVALID_PARAMETER;

I = args.front();
while (I) {
Expand Down Expand Up @@ -909,12 +911,12 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph
} else if (I->get() == "-h" || I->get() == "--help" || I->get() == "/?") { // display help

show_help = true;
exit_code = ERR_HELP; // Hack to force an early exit in `main()` with a success code.
exit_err = ERR_HELP; // Hack to force an early exit in `main()` with a success code.
goto error;

} else if (I->get() == "--version") {
print_line(get_full_version_string());
exit_code = ERR_HELP; // Hack to force an early exit in `main()` with a success code.
exit_err = ERR_HELP; // Hack to force an early exit in `main()` with a success code.
goto error;

} else if (I->get() == "-v" || I->get() == "--verbose") { // verbose output
Expand Down Expand Up @@ -2243,7 +2245,7 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph
OS::get_singleton()->finalize_core();
locale = String();

return exit_code;
return exit_err;
}

Error _parse_resource_dummy(void *p_data, VariantParser::Stream *p_stream, Ref<Resource> &r_res, int &line, String &r_err_str) {
Expand Down Expand Up @@ -2778,7 +2780,10 @@ String Main::get_rendering_driver_name() {
// everything the main loop needs to know about frame timings
static MainTimerSync main_timer_sync;

bool Main::start() {
// Return value should be EXIT_SUCCESS if we start successfully
// and should move on to `OS::run`, and EXIT_FAILURE otherwise for
// an early exit with that error code.
int Main::start() {
ERR_FAIL_COND_V(!_start_success, false);

bool has_icon = false;
Expand Down Expand Up @@ -2912,7 +2917,7 @@ bool Main::start() {

{
Ref<DirAccess> da = DirAccess::open(doc_tool_path);
ERR_FAIL_COND_V_MSG(da.is_null(), false, "Argument supplied to --doctool must be a valid directory path.");
ERR_FAIL_COND_V_MSG(da.is_null(), EXIT_FAILURE, "Argument supplied to --doctool must be a valid directory path.");
}

#ifndef MODULE_MONO_ENABLED
Expand Down Expand Up @@ -2947,23 +2952,23 @@ bool Main::start() {
// Create the module documentation directory if it doesn't exist
Ref<DirAccess> da = DirAccess::create_for_path(path);
err = da->make_dir_recursive(path);
ERR_FAIL_COND_V_MSG(err != OK, false, "Error: Can't create directory: " + path + ": " + itos(err));
ERR_FAIL_COND_V_MSG(err != OK, EXIT_FAILURE, "Error: Can't create directory: " + path + ": " + itos(err));

print_line("Loading docs from: " + path);
err = docsrc.load_classes(path);
ERR_FAIL_COND_V_MSG(err != OK, false, "Error loading docs from: " + path + ": " + itos(err));
ERR_FAIL_COND_V_MSG(err != OK, EXIT_FAILURE, "Error loading docs from: " + path + ": " + itos(err));
}
}

String index_path = doc_tool_path.path_join("doc/classes");
// Create the main documentation directory if it doesn't exist
Ref<DirAccess> da = DirAccess::create_for_path(index_path);
err = da->make_dir_recursive(index_path);
ERR_FAIL_COND_V_MSG(err != OK, false, "Error: Can't create index directory: " + index_path + ": " + itos(err));
ERR_FAIL_COND_V_MSG(err != OK, EXIT_FAILURE, "Error: Can't create index directory: " + index_path + ": " + itos(err));

print_line("Loading classes from: " + index_path);
err = docsrc.load_classes(index_path);
ERR_FAIL_COND_V_MSG(err != OK, false, "Error loading classes from: " + index_path + ": " + itos(err));
ERR_FAIL_COND_V_MSG(err != OK, EXIT_FAILURE, "Error loading classes from: " + index_path + ": " + itos(err));
checked_paths.insert(index_path);

print_line("Merging docs...");
Expand All @@ -2972,20 +2977,19 @@ bool Main::start() {
for (const String &E : checked_paths) {
print_line("Erasing old docs at: " + E);
err = DocTools::erase_classes(E);
ERR_FAIL_COND_V_MSG(err != OK, false, "Error erasing old docs at: " + E + ": " + itos(err));
ERR_FAIL_COND_V_MSG(err != OK, EXIT_FAILURE, "Error erasing old docs at: " + E + ": " + itos(err));
}

print_line("Generating new docs...");
err = doc.save_classes(index_path, doc_data_classes);
ERR_FAIL_COND_V_MSG(err != OK, false, "Error saving new docs:" + itos(err));
ERR_FAIL_COND_V_MSG(err != OK, EXIT_FAILURE, "Error saving new docs:" + itos(err));

print_line("Deleting docs cache...");
if (FileAccess::exists(EditorHelp::get_cache_full_path())) {
DirAccess::remove_file_or_error(EditorHelp::get_cache_full_path());
}

OS::get_singleton()->set_exit_code(EXIT_SUCCESS);
return false;
return EXIT_SUCCESS;
}

if (dump_gdextension_interface) {
Expand All @@ -2998,30 +3002,22 @@ bool Main::start() {
}

if (dump_gdextension_interface || dump_extension_api) {
OS::get_singleton()->set_exit_code(EXIT_SUCCESS);
return false;
return EXIT_SUCCESS;
}

if (validate_extension_api) {
bool valid = GDExtensionAPIDump::validate_extension_json_file(validate_extension_api_file) == OK;
OS::get_singleton()->set_exit_code(valid ? EXIT_SUCCESS : EXIT_FAILURE);
return false;
return valid ? EXIT_SUCCESS : EXIT_FAILURE;
}

#ifndef DISABLE_DEPRECATED
if (converting_project) {
int ret = ProjectConverter3To4(converter_max_kb_file, converter_max_line_length).convert();
if (ret) {
OS::get_singleton()->set_exit_code(EXIT_SUCCESS);
}
return false;
return ret ? EXIT_SUCCESS : EXIT_FAILURE;
}
if (validating_converting_project) {
bool ret = ProjectConverter3To4(converter_max_kb_file, converter_max_line_length).validate_conversion();
if (ret) {
OS::get_singleton()->set_exit_code(EXIT_SUCCESS);
}
return false;
return ret ? EXIT_SUCCESS : EXIT_FAILURE;
}
#endif // DISABLE_DEPRECATED

Expand All @@ -3038,7 +3034,7 @@ bool Main::start() {
// this might end up triggered by valid usage, in which case we'll have to
// fine-tune further.
OS::get_singleton()->alert("Couldn't detect whether to run the editor, the project manager or a specific project. Aborting.");
ERR_FAIL_V_MSG(false, "Couldn't detect whether to run the editor, the project manager or a specific project. Aborting.");
ERR_FAIL_V_MSG(EXIT_FAILURE, "Couldn't detect whether to run the editor, the project manager or a specific project. Aborting.");
}
#endif

Expand All @@ -3052,15 +3048,10 @@ bool Main::start() {

if (!script.is_empty()) {
Ref<Script> script_res = ResourceLoader::load(script);
ERR_FAIL_COND_V_MSG(script_res.is_null(), false, "Can't load script: " + script);
ERR_FAIL_COND_V_MSG(script_res.is_null(), EXIT_FAILURE, "Can't load script: " + script);

if (check_only) {
if (!script_res->is_valid()) {
OS::get_singleton()->set_exit_code(EXIT_FAILURE);
} else {
OS::get_singleton()->set_exit_code(EXIT_SUCCESS);
}
return false;
return script_res->is_valid() ? EXIT_SUCCESS : EXIT_FAILURE;
}

if (script_res->can_instantiate()) {
Expand All @@ -3072,21 +3063,21 @@ bool Main::start() {
memdelete(obj);
}
OS::get_singleton()->alert(vformat("Can't load the script \"%s\" as it doesn't inherit from SceneTree or MainLoop.", script));
ERR_FAIL_V_MSG(false, vformat("Can't load the script \"%s\" as it doesn't inherit from SceneTree or MainLoop.", script));
ERR_FAIL_V_MSG(EXIT_FAILURE, vformat("Can't load the script \"%s\" as it doesn't inherit from SceneTree or MainLoop.", script));
}

script_loop->set_script(script_res);
main_loop = script_loop;
} else {
return false;
return EXIT_FAILURE;
}
} else { // Not based on script path.
if (!editor && !ClassDB::class_exists(main_loop_type) && ScriptServer::is_global_class(main_loop_type)) {
String script_path = ScriptServer::get_global_class_path(main_loop_type);
Ref<Script> script_res = ResourceLoader::load(script_path);
if (script_res.is_null()) {
OS::get_singleton()->alert("Error: Could not load MainLoop script type: " + main_loop_type);
ERR_FAIL_V_MSG(false, vformat("Could not load global class %s.", main_loop_type));
ERR_FAIL_V_MSG(EXIT_FAILURE, vformat("Could not load global class %s.", main_loop_type));
}
StringName script_base = script_res->get_instance_base_type();
Object *obj = ClassDB::instantiate(script_base);
Expand All @@ -3096,7 +3087,7 @@ bool Main::start() {
memdelete(obj);
}
OS::get_singleton()->alert("Error: Invalid MainLoop script base type: " + script_base);
ERR_FAIL_V_MSG(false, vformat("The global class %s does not inherit from SceneTree or MainLoop.", main_loop_type));
ERR_FAIL_V_MSG(EXIT_FAILURE, vformat("The global class %s does not inherit from SceneTree or MainLoop.", main_loop_type));
}
script_loop->set_script(script_res);
main_loop = script_loop;
Expand All @@ -3110,15 +3101,15 @@ bool Main::start() {
if (!main_loop) {
if (!ClassDB::class_exists(main_loop_type)) {
OS::get_singleton()->alert("Error: MainLoop type doesn't exist: " + main_loop_type);
return false;
return EXIT_FAILURE;
} else {
Object *ml = ClassDB::instantiate(main_loop_type);
ERR_FAIL_NULL_V_MSG(ml, false, "Can't instance MainLoop type.");
ERR_FAIL_NULL_V_MSG(ml, EXIT_FAILURE, "Can't instance MainLoop type.");

main_loop = Object::cast_to<MainLoop>(ml);
if (!main_loop) {
memdelete(ml);
ERR_FAIL_V_MSG(false, "Invalid MainLoop type.");
ERR_FAIL_V_MSG(EXIT_FAILURE, "Invalid MainLoop type.");
}
}
}
Expand Down Expand Up @@ -3243,7 +3234,7 @@ bool Main::start() {
Error err;

Vector<String> paths = get_files_with_extension(gdscript_docs_path, "gd");
ERR_FAIL_COND_V_MSG(paths.size() == 0, false, "Couldn't find any GDScript files under the given directory: " + gdscript_docs_path);
ERR_FAIL_COND_V_MSG(paths.size() == 0, EXIT_FAILURE, "Couldn't find any GDScript files under the given directory: " + gdscript_docs_path);

for (const String &path : paths) {
Ref<GDScript> gdscript = ResourceLoader::load(path);
Expand All @@ -3258,14 +3249,13 @@ bool Main::start() {

Ref<DirAccess> da = DirAccess::create_for_path(doc_tool_path);
err = da->make_dir_recursive(doc_tool_path);
ERR_FAIL_COND_V_MSG(err != OK, false, "Error: Can't create GDScript docs directory: " + doc_tool_path + ": " + itos(err));
ERR_FAIL_COND_V_MSG(err != OK, EXIT_FAILURE, "Error: Can't create GDScript docs directory: " + doc_tool_path + ": " + itos(err));

HashMap<String, String> doc_data_classes;
err = docs.save_classes(doc_tool_path, doc_data_classes, false);
ERR_FAIL_COND_V_MSG(err != OK, false, "Error saving GDScript docs:" + itos(err));
ERR_FAIL_COND_V_MSG(err != OK, EXIT_FAILURE, "Error saving GDScript docs:" + itos(err));

OS::get_singleton()->set_exit_code(EXIT_SUCCESS);
return false;
return EXIT_SUCCESS;
}
#endif // MODULE_GDSCRIPT_ENABLED

Expand Down Expand Up @@ -3380,7 +3370,7 @@ bool Main::start() {

if (sep == -1) {
Ref<DirAccess> da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
ERR_FAIL_COND_V(da.is_null(), false);
ERR_FAIL_COND_V(da.is_null(), EXIT_FAILURE);

local_game_path = da->get_current_dir().path_join(local_game_path);
} else {
Expand Down Expand Up @@ -3430,7 +3420,7 @@ bool Main::start() {
scene = scenedata->instantiate();
}

ERR_FAIL_NULL_V_MSG(scene, false, "Failed loading scene: " + local_game_path + ".");
ERR_FAIL_NULL_V_MSG(scene, EXIT_FAILURE, "Failed loading scene: " + local_game_path + ".");
sml->add_current_scene(scene);

#ifdef MACOS_ENABLED
Expand Down Expand Up @@ -3503,7 +3493,7 @@ bool Main::start() {
OS::get_singleton()->benchmark_end_measure("startup_begin");
OS::get_singleton()->benchmark_dump();

return true;
return EXIT_SUCCESS;
}

/* Main iteration
Expand Down Expand Up @@ -3533,10 +3523,10 @@ static uint64_t physics_process_max = 0;
static uint64_t process_max = 0;
static uint64_t navigation_process_max = 0;

// Return false means iterating further, returning true means `OS::run`
// will terminate the program. In case of failure, the OS exit code needs
// to be set explicitly here (defaults to EXIT_SUCCESS).
bool Main::iteration() {
//for now do not error on this
//ERR_FAIL_COND_V(iterating, false);

iterating++;

const uint64_t ticks = OS::get_singleton()->get_ticks_usec();
Expand Down
2 changes: 1 addition & 1 deletion main/main.h
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ class Main {
static Error test_setup();
static void test_cleanup();
#endif
static bool start();
static int start();

static bool iteration();
static void force_redraw();
Expand Down
11 changes: 6 additions & 5 deletions platform/ios/godot_ios.mm
Original file line number Diff line number Diff line change
Expand Up @@ -102,15 +102,16 @@ int ios_main(int argc, char **argv) {

Error err = Main::setup(fargv[0], argc - 1, &fargv[1], false);

if (err == ERR_HELP) { // Returned by --help and --version, so success.
return 0;
} else if (err != OK) {
return 255;
if (err != OK) {
if (err == ERR_HELP) { // Returned by --help and --version, so success.
return EXIT_SUCCESS;
}
return EXIT_FAILURE;
}

os->initialize_modules();

return 0;
return os->get_exit_code();
}

void ios_finish() {
Expand Down
13 changes: 7 additions & 6 deletions platform/linuxbsd/godot_linuxbsd.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -60,18 +60,19 @@ int main(int argc, char *argv[]) {
char *ret = getcwd(cwd, PATH_MAX);

Error err = Main::setup(argv[0], argc - 1, &argv[1]);

if (err != OK) {
free(cwd);

if (err == ERR_HELP) { // Returned by --help and --version, so success.
return 0;
return EXIT_SUCCESS;
}
return 255;
return EXIT_FAILURE;
}

if (Main::start()) {
os.set_exit_code(EXIT_SUCCESS);
os.run(); // it is actually the OS that decides how to run
if (Main::start() == EXIT_SUCCESS) {
os.run();
} else {
os.set_exit_code(EXIT_FAILURE);
}
Main::cleanup();

Expand Down
18 changes: 12 additions & 6 deletions platform/macos/godot_main_macos.mm
Original file line number Diff line number Diff line change
Expand Up @@ -67,14 +67,20 @@ int main(int argc, char **argv) {

err = Main::setup(argv[0], argc - first_arg, &argv[first_arg]);

if (err == ERR_HELP) { // Returned by --help and --version, so success.
return 0;
} else if (err != OK) {
return 255;
if (err != OK) {
if (err == ERR_HELP) { // Returned by --help and --version, so success.
return EXIT_SUCCESS;
}
return EXIT_FAILURE;
}

if (Main::start()) {
os.run(); // It is actually the OS that decides how to run.
int ret;
ret = Main::start();

if (ret) {
os.run();
} else {
os.set_exit_code(EXIT_FAILURE);
}

Main::cleanup();
Expand Down
Loading

0 comments on commit 3dd6485

Please sign in to comment.