diff --git a/core/config/project_settings.cpp b/core/config/project_settings.cpp
index 93934f23205a..31260946ad76 100644
--- a/core/config/project_settings.cpp
+++ b/core/config/project_settings.cpp
@@ -1299,6 +1299,7 @@ ProjectSettings::ProjectSettings() {
GLOBAL_DEF_BASIC(PropertyInfo(Variant::DICTIONARY, "application/config/name_localized", PROPERTY_HINT_LOCALIZABLE_STRING), Dictionary());
GLOBAL_DEF_BASIC(PropertyInfo(Variant::STRING, "application/config/description", PROPERTY_HINT_MULTILINE_TEXT), "");
GLOBAL_DEF_BASIC("application/config/version", "");
+ GLOBAL_DEF_BASIC("application/config/build", 1);
GLOBAL_DEF_INTERNAL(PropertyInfo(Variant::STRING, "application/config/tags"), PackedStringArray());
GLOBAL_DEF_BASIC(PropertyInfo(Variant::STRING, "application/run/main_scene", PROPERTY_HINT_FILE, "*.tscn,*.scn,*.res"), "");
GLOBAL_DEF("application/run/disable_stdout", false);
diff --git a/doc/classes/ProjectSettings.xml b/doc/classes/ProjectSettings.xml
index 3fc9e94427ec..627ec5c2f14e 100644
--- a/doc/classes/ProjectSettings.xml
+++ b/doc/classes/ProjectSettings.xml
@@ -261,6 +261,9 @@
If [code]true[/code], the application automatically accepts quitting requests.
+
+ The project's machine-readable build number. This value is automatically incremented on successful project export.
+
This user directory is used for storing persistent data ([code]user://[/code] filesystem). If a custom directory name is defined, this name will be appended to the system-specific user data directory (same parent folder as the Godot configuration folder documented in [method OS.get_user_data_dir]).
The [member application/config/use_custom_user_dir] setting must be enabled for this to take effect.
diff --git a/drivers/vulkan/vulkan_context.cpp b/drivers/vulkan/vulkan_context.cpp
index 7a397a170de6..e1a102dc5a90 100644
--- a/drivers/vulkan/vulkan_context.cpp
+++ b/drivers/vulkan/vulkan_context.cpp
@@ -1028,7 +1028,7 @@ Error VulkanContext::_create_instance() {
/*sType*/ VK_STRUCTURE_TYPE_APPLICATION_INFO,
/*pNext*/ nullptr,
/*pApplicationName*/ cs.get_data(),
- /*applicationVersion*/ 0, // It would be really nice if we store a version number in project settings, say "application/config/version"
+ /*applicationVersion*/ GLOBAL_GET("application/config/build").operator uint32_t(),
/*pEngineName*/ VERSION_NAME,
/*engineVersion*/ VK_MAKE_VERSION(VERSION_MAJOR, VERSION_MINOR, VERSION_PATCH),
/*apiVersion*/ application_api_version
diff --git a/editor/editor_node.cpp b/editor/editor_node.cpp
index b2a65063a767..8ffa291f0703 100644
--- a/editor/editor_node.cpp
+++ b/editor/editor_node.cpp
@@ -979,7 +979,14 @@ void EditorNode::_fs_changed() {
err = missing_templates ? ERR_FILE_NOT_FOUND : ERR_UNCONFIGURED;
} else {
platform->clear_messages();
+ uint32_t old_build_nr = ProjectSettings::get_singleton()->get_setting("application/config/build");
+ ProjectSettings::get_singleton()->set_setting("application/config/build", old_build_nr + 1); /// Increase build number.
err = platform->export_project(export_preset, export_defer.debug, export_path);
+ if (err == OK) {
+ ProjectSettings::get_singleton()->save();
+ } else {
+ ProjectSettings::get_singleton()->set_setting("application/config/build", old_build_nr); // Reset build number.
+ }
}
}
if (err != OK) {
diff --git a/editor/export/editor_export_preset.cpp b/editor/export/editor_export_preset.cpp
index b941170b7bc0..4be94f4f9eba 100644
--- a/editor/export/editor_export_preset.cpp
+++ b/editor/export/editor_export_preset.cpp
@@ -344,10 +344,22 @@ _FORCE_INLINE_ bool _check_digits(const String &p_str) {
return true;
}
+String EditorExportPreset::get_build_nr(const StringName &p_preset_string) const {
+ String result = get(p_preset_string);
+ if (result.is_empty()) {
+ result = GLOBAL_GET("application/config/build");
+ if (result.is_empty()) {
+ result = "1";
+ }
+ }
+ return result;
+}
+
String EditorExportPreset::get_version(const StringName &p_preset_string, bool p_windows_version) const {
String result = get(p_preset_string);
if (result.is_empty()) {
result = GLOBAL_GET("application/config/version");
+ String build_nr = GLOBAL_GET("application/config/build");
// Split and validate version number components.
const PackedStringArray result_split = result.split(".", false);
@@ -363,11 +375,11 @@ String EditorExportPreset::get_version(const StringName &p_preset_string, bool p
if (p_windows_version) {
// Modify version number to match Windows constraints (version numbers must have 4 components).
if (result_split.size() == 1) {
- result = result + ".0.0.0";
+ result = result + ".0.0." + build_nr;
} else if (result_split.size() == 2) {
- result = result + ".0.0";
+ result = result + ".0." + build_nr;
} else if (result_split.size() == 3) {
- result = result + ".0";
+ result = result + "." + build_nr;
} else {
result = vformat("%s.%s.%s.%s", result_split[0], result_split[1], result_split[2], result_split[3]);
}
@@ -379,7 +391,7 @@ String EditorExportPreset::get_version(const StringName &p_preset_string, bool p
WARN_PRINT(vformat("Invalid version number \"%s\". The version number can only contain numeric characters (0-9) and non-consecutive periods (.).", result));
}
if (p_windows_version) {
- result = "1.0.0.0";
+ result = "1.0.0." + build_nr;
} else {
result = "1.0.0";
}
diff --git a/editor/export/editor_export_preset.h b/editor/export/editor_export_preset.h
index 025e7603f346..9c484ce42527 100644
--- a/editor/export/editor_export_preset.h
+++ b/editor/export/editor_export_preset.h
@@ -160,6 +160,7 @@ class EditorExportPreset : public RefCounted {
// be compatible with Windows executable metadata (which requires a
// 4-component format).
String get_version(const StringName &p_name, bool p_windows_version = false) const;
+ String get_build_nr(const StringName &p_name) const;
const HashMap &get_properties() const { return properties; }
const HashMap &get_values() const { return values; }
diff --git a/editor/export/project_export.cpp b/editor/export/project_export.cpp
index 719c3114f4b9..099d921f09c9 100644
--- a/editor/export/project_export.cpp
+++ b/editor/export/project_export.cpp
@@ -1084,7 +1084,13 @@ void ProjectExportDialog::_export_project_to_path(const String &p_path) {
exporting = true;
platform->clear_messages();
+
+ uint32_t old_build_nr = ProjectSettings::get_singleton()->get_setting("application/config/build");
+ ProjectSettings::get_singleton()->set_setting("application/config/build", old_build_nr + 1); // Increase build number.
Error err = platform->export_project(current, export_debug->is_pressed(), current->get_export_path(), 0);
+ if (err != OK) {
+ ProjectSettings::get_singleton()->set_setting("application/config/build", old_build_nr); // Reset build number.
+ }
result_dialog_log->clear();
if (err != ERR_SKIP) {
if (platform->fill_log_messages(result_dialog_log, err)) {
@@ -1114,7 +1120,11 @@ void ProjectExportDialog::_export_all(bool p_debug) {
exporting = true;
+ uint32_t old_build_nr = ProjectSettings::get_singleton()->get_setting("application/config/build");
+ ProjectSettings::get_singleton()->set_setting("application/config/build", old_build_nr + 1); // Increase build number.
+
bool show_dialog = false;
+ bool all_exports_fail = true;
result_dialog_log->clear();
for (int i = 0; i < EditorExport::get_singleton()->get_export_preset_count(); i++) {
Ref preset = EditorExport::get_singleton()->get_export_preset(i);
@@ -1133,13 +1143,20 @@ void ProjectExportDialog::_export_all(bool p_debug) {
platform->clear_messages();
Error err = platform->export_project(preset, p_debug, preset->get_export_path(), 0);
+ if (err == OK) {
+ all_exports_fail = false; // At least one export was successful, keep new build number.
+ }
if (err == ERR_SKIP) {
exporting = false;
+ show_dialog = false;
return;
}
bool has_messages = platform->fill_log_messages(result_dialog_log, err);
show_dialog = show_dialog || has_messages;
}
+ if (all_exports_fail) {
+ ProjectSettings::get_singleton()->set_setting("application/config/build", old_build_nr); // Reset build number.
+ }
if (show_dialog) {
result_dialog->popup_centered_ratio(0.5);
}
diff --git a/platform/android/doc_classes/EditorExportPlatformAndroid.xml b/platform/android/doc_classes/EditorExportPlatformAndroid.xml
index dae968378b89..f971104076ab 100644
--- a/platform/android/doc_classes/EditorExportPlatformAndroid.xml
+++ b/platform/android/doc_classes/EditorExportPlatformAndroid.xml
@@ -583,7 +583,7 @@
If [code]true[/code], allows the application to participate in the backup and restore infrastructure.
-
+
Machine-readable application version. This must be incremented for every new release pushed to the Play Store.
diff --git a/platform/android/export/export_plugin.cpp b/platform/android/export/export_plugin.cpp
index f0ee405b41fb..42abf30db10e 100644
--- a/platform/android/export/export_plugin.cpp
+++ b/platform/android/export/export_plugin.cpp
@@ -906,7 +906,7 @@ void EditorExportPlatformAndroid::_fix_manifest(const Ref &p
Vector stable_extra;
String version_name = p_preset->get_version("version/name");
- int version_code = p_preset->get("version/code");
+ String version_code = p_preset->get_build_nr("version/code");
String package_name = p_preset->get("package/unique_name");
const int screen_orientation =
@@ -998,7 +998,7 @@ void EditorExportPlatformAndroid::_fix_manifest(const Ref &p
}
if (tname == "manifest" && attrname == "versionCode") {
- encode_uint32(version_code, &p_manifest.write[iofs + 16]);
+ encode_uint32(version_code.to_int(), &p_manifest.write[iofs + 16]);
}
if (tname == "manifest" && attrname == "versionName") {
@@ -1835,7 +1835,7 @@ void EditorExportPlatformAndroid::get_export_options(List *r_optio
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "keystore/release_user", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_SECRET), ""));
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "keystore/release_password", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_SECRET), ""));
- r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "version/code", PROPERTY_HINT_RANGE, "1,4096,1,or_greater"), 1));
+ r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "version/code", PROPERTY_HINT_PLACEHOLDER_TEXT, "Leave empty to use project build number"), ""));
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "version/name", PROPERTY_HINT_PLACEHOLDER_TEXT, "Leave empty to use project version"), ""));
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "package/unique_name", PROPERTY_HINT_PLACEHOLDER_TEXT, "ext.domain.name"), "com.example.$genname", false, true));
@@ -2458,9 +2458,9 @@ List EditorExportPlatformAndroid::get_binary_extensions(const Ref &p_preset, const String &p_path) {
- int version_code = p_preset->get("version/code");
+ String version_code = p_preset->get_build_nr("version/code");
String package_name = p_preset->get("package/unique_name");
- String apk_file_name = "main." + itos(version_code) + "." + get_package_name(package_name) + ".obb";
+ String apk_file_name = "main." + version_code + "." + get_package_name(package_name) + ".obb";
String fullpath = p_path.get_base_dir().path_join(apk_file_name);
return fullpath;
}
@@ -2916,7 +2916,7 @@ Error EditorExportPlatformAndroid::export_project_helper(const Refget("package/unique_name"));
- String version_code = itos(p_preset->get("version/code"));
+ String version_code = p_preset->get_build_nr("version/code");
String version_name = p_preset->get_version("version/name");
String min_sdk_version = p_preset->get("gradle_build/min_sdk");
if (!min_sdk_version.is_valid_int()) {
diff --git a/platform/ios/export/export_plugin.cpp b/platform/ios/export/export_plugin.cpp
index c0e052865fce..e513617468ca 100644
--- a/platform/ios/export/export_plugin.cpp
+++ b/platform/ios/export/export_plugin.cpp
@@ -179,7 +179,7 @@ void EditorExportPlatformIOS::get_export_options(List *r_options)
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/bundle_identifier", PROPERTY_HINT_PLACEHOLDER_TEXT, "com.example.game"), "", false, true));
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/signature"), ""));
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/short_version", PROPERTY_HINT_PLACEHOLDER_TEXT, "Leave empty to use project version"), ""));
- r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/version", PROPERTY_HINT_PLACEHOLDER_TEXT, "Leave empty to use project version"), ""));
+ r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/version", PROPERTY_HINT_PLACEHOLDER_TEXT, "Leave empty to use project build number"), ""));
r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "application/icon_interpolation", PROPERTY_HINT_ENUM, "Nearest neighbor,Bilinear,Cubic,Trilinear,Lanczos"), 4));
r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "application/launch_screens_interpolation", PROPERTY_HINT_ENUM, "Nearest neighbor,Bilinear,Cubic,Trilinear,Lanczos"), 4));
@@ -286,7 +286,7 @@ void EditorExportPlatformIOS::_fix_config_file(const Ref &p_
} else if (lines[i].find("$short_version") != -1) {
strnew += lines[i].replace("$short_version", p_preset->get_version("application/short_version")) + "\n";
} else if (lines[i].find("$version") != -1) {
- strnew += lines[i].replace("$version", p_preset->get_version("application/version")) + "\n";
+ strnew += lines[i].replace("$version", p_preset->get_build_nr("application/version")) + "\n";
} else if (lines[i].find("$signature") != -1) {
strnew += lines[i].replace("$signature", p_preset->get("application/signature")) + "\n";
} else if (lines[i].find("$team_id") != -1) {
diff --git a/platform/macos/export/export_plugin.cpp b/platform/macos/export/export_plugin.cpp
index 24cb76b4ab7a..3196acc80c4d 100644
--- a/platform/macos/export/export_plugin.cpp
+++ b/platform/macos/export/export_plugin.cpp
@@ -381,7 +381,7 @@ void EditorExportPlatformMacOS::get_export_options(List *r_options
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/signature"), ""));
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/app_category", PROPERTY_HINT_ENUM, "Business,Developer-tools,Education,Entertainment,Finance,Games,Action-games,Adventure-games,Arcade-games,Board-games,Card-games,Casino-games,Dice-games,Educational-games,Family-games,Kids-games,Music-games,Puzzle-games,Racing-games,Role-playing-games,Simulation-games,Sports-games,Strategy-games,Trivia-games,Word-games,Graphics-design,Healthcare-fitness,Lifestyle,Medical,Music,News,Photography,Productivity,Reference,Social-networking,Sports,Travel,Utilities,Video,Weather"), "Games"));
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/short_version", PROPERTY_HINT_PLACEHOLDER_TEXT, "Leave empty to use project version"), ""));
- r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/version", PROPERTY_HINT_PLACEHOLDER_TEXT, "Leave empty to use project version"), ""));
+ r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/version", PROPERTY_HINT_PLACEHOLDER_TEXT, "Leave empty to use project build number"), ""));
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/copyright"), ""));
r_options->push_back(ExportOption(PropertyInfo(Variant::DICTIONARY, "application/copyright_localized", PROPERTY_HINT_LOCALIZABLE_STRING), Dictionary()));
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/min_macos_version"), "10.12"));
@@ -660,7 +660,7 @@ void EditorExportPlatformMacOS::_fix_plist(const Ref &p_pres
} else if (lines[i].find("$short_version") != -1) {
strnew += lines[i].replace("$short_version", p_preset->get_version("application/short_version")) + "\n";
} else if (lines[i].find("$version") != -1) {
- strnew += lines[i].replace("$version", p_preset->get_version("application/version")) + "\n";
+ strnew += lines[i].replace("$version", p_preset->get_build_nr("application/version")) + "\n";
} else if (lines[i].find("$signature") != -1) {
strnew += lines[i].replace("$signature", p_preset->get("application/signature")) + "\n";
} else if (lines[i].find("$app_category") != -1) {
diff --git a/platform/windows/export/export_plugin.cpp b/platform/windows/export/export_plugin.cpp
index 4185c36d7731..a384f82f2f59 100644
--- a/platform/windows/export/export_plugin.cpp
+++ b/platform/windows/export/export_plugin.cpp
@@ -372,8 +372,8 @@ void EditorExportPlatformWindows::get_export_options(List *r_optio
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/icon", PROPERTY_HINT_FILE, "*.ico,*.png,*.webp,*.svg"), "", false, true));
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/console_wrapper_icon", PROPERTY_HINT_FILE, "*.ico,*.png,*.webp,*.svg"), ""));
r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "application/icon_interpolation", PROPERTY_HINT_ENUM, "Nearest neighbor,Bilinear,Cubic,Trilinear,Lanczos"), 4));
- r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/file_version", PROPERTY_HINT_PLACEHOLDER_TEXT, "Leave empty to use project version"), ""));
- r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/product_version", PROPERTY_HINT_PLACEHOLDER_TEXT, "Leave empty to use project version"), ""));
+ r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/file_version", PROPERTY_HINT_PLACEHOLDER_TEXT, "Leave empty to use project version and build number"), ""));
+ r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/product_version", PROPERTY_HINT_PLACEHOLDER_TEXT, "Leave empty to use project version and build number"), ""));
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/company_name", PROPERTY_HINT_PLACEHOLDER_TEXT, "Company Name"), ""));
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/product_name", PROPERTY_HINT_PLACEHOLDER_TEXT, "Game Name"), ""));
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/file_description"), ""));