From 011965d8640cfcc7844b90fac0910b1e53bfda03 Mon Sep 17 00:00:00 2001 From: David Snopek Date: Tue, 20 Jun 2023 10:03:15 -0500 Subject: [PATCH 1/7] Attempt to fully implement CharString (cherry picked from commit 4df112cd95840313600d0f850ec52a56d0961386) --- include/godot_cpp/templates/cowdata.hpp | 8 +- include/godot_cpp/variant/char_string.hpp | 131 +++++++------ src/variant/char_string.cpp | 225 +++++++++++----------- test/project/main.gd | 4 + test/src/example.cpp | 5 + test/src/example.h | 1 + 6 files changed, 208 insertions(+), 166 deletions(-) diff --git a/include/godot_cpp/templates/cowdata.hpp b/include/godot_cpp/templates/cowdata.hpp index 1753687e..18320d80 100644 --- a/include/godot_cpp/templates/cowdata.hpp +++ b/include/godot_cpp/templates/cowdata.hpp @@ -32,13 +32,13 @@ #define GODOT_COWDATA_HPP #include -#include #include #include #include #include #include +#include namespace godot { @@ -48,6 +48,9 @@ class Vector; template class VMap; +template +class CharStringT; + // Silence a false positive warning (see GH-52119). #if defined(__GNUC__) && !defined(__clang__) #pragma GCC diagnostic push @@ -62,6 +65,9 @@ class CowData { template friend class VMap; + template + friend class CharStringT; + private: mutable T *_ptr = nullptr; diff --git a/include/godot_cpp/variant/char_string.hpp b/include/godot_cpp/variant/char_string.hpp index fa58bbdf..f8f51696 100644 --- a/include/godot_cpp/variant/char_string.hpp +++ b/include/godot_cpp/variant/char_string.hpp @@ -31,82 +31,99 @@ #ifndef GODOT_CHAR_STRING_HPP #define GODOT_CHAR_STRING_HPP +#include + #include #include namespace godot { -class CharString { - friend class String; +template +class CharStringT; - const char *_data = nullptr; - int _length = 0; +template +class CharProxy { + template + friend class CharStringT; - CharString(const char *str, int length); + const int _index; + CowData &_cowdata; + static inline const T _null = 0; + + _FORCE_INLINE_ CharProxy(const int &p_index, CowData &p_cowdata) : + _index(p_index), + _cowdata(p_cowdata) {} public: - int length() const; - const char *get_data() const; + _FORCE_INLINE_ CharProxy(const CharProxy &p_other) : + _index(p_other._index), + _cowdata(p_other._cowdata) {} - CharString(CharString &&p_str); - void operator=(CharString &&p_str); - CharString() {} - ~CharString(); + _FORCE_INLINE_ operator T() const { + if (unlikely(_index == _cowdata.size())) { + return _null; + } + + return _cowdata.get(_index); + } + + _FORCE_INLINE_ const T *operator&() const { + return _cowdata.ptr() + _index; + } + + _FORCE_INLINE_ void operator=(const T &p_other) const { + _cowdata.set(_index, p_other); + } + + _FORCE_INLINE_ void operator=(const CharProxy &p_other) const { + _cowdata.set(_index, p_other.operator T()); + } }; -class Char16String { +template +class CharStringT { friend class String; - const char16_t *_data = nullptr; - int _length = 0; - - Char16String(const char16_t *str, int length); + CowData _cowdata; + static inline const T _null = 0; public: - int length() const; - const char16_t *get_data() const; + _FORCE_INLINE_ T *ptrw() { return _cowdata.ptrw(); } + _FORCE_INLINE_ const T *ptr() const { return _cowdata.ptr(); } + _FORCE_INLINE_ int size() const { return _cowdata.size(); } + Error resize(int p_size) { return _cowdata.resize(p_size); } - Char16String(Char16String &&p_str); - void operator=(Char16String &&p_str); - Char16String() {} - ~Char16String(); + _FORCE_INLINE_ T get(int p_index) const { return _cowdata.get(p_index); } + _FORCE_INLINE_ void set(int p_index, const T &p_elem) { _cowdata.set(p_index, p_elem); } + _FORCE_INLINE_ const T &operator[](int p_index) const { + if (unlikely(p_index == _cowdata.size())) { + return _null; + } + + return _cowdata.get(p_index); + } + _FORCE_INLINE_ CharProxy operator[](int p_index) { return CharProxy(p_index, _cowdata); } + + _FORCE_INLINE_ CharStringT() {} + _FORCE_INLINE_ CharStringT(const CharStringT &p_str) { _cowdata._ref(p_str._cowdata); } + _FORCE_INLINE_ void operator=(const CharStringT &p_str) { _cowdata._ref(p_str._cowdata); } + _FORCE_INLINE_ CharStringT(const T *p_cstr) { copy_from(p_cstr); } + + void operator=(const T *p_cstr); + bool operator<(const CharStringT &p_right) const; + CharStringT &operator+=(T p_char); + int length() const { return size() ? size() - 1 : 0; } + const T *get_data() const; + operator const T *() const { return get_data(); }; + +protected: + void copy_from(const T *p_cstr); }; -class Char32String { - friend class String; - - const char32_t *_data = nullptr; - int _length = 0; - - Char32String(const char32_t *str, int length); - -public: - int length() const; - const char32_t *get_data() const; - - Char32String(Char32String &&p_str); - void operator=(Char32String &&p_str); - Char32String() {} - ~Char32String(); -}; - -class CharWideString { - friend class String; - - const wchar_t *_data = nullptr; - int _length = 0; - - CharWideString(const wchar_t *str, int length); - -public: - int length() const; - const wchar_t *get_data() const; - - CharWideString(CharWideString &&p_str); - void operator=(CharWideString &&p_str); - CharWideString() {} - ~CharWideString(); -}; +typedef CharStringT CharString; +typedef CharStringT Char16String; +typedef CharStringT Char32String; +typedef CharStringT CharWideString; } // namespace godot diff --git a/src/variant/char_string.cpp b/src/variant/char_string.cpp index 0c8cd0fb..856037c4 100644 --- a/src/variant/char_string.cpp +++ b/src/variant/char_string.cpp @@ -38,117 +38,121 @@ #include #include +#include namespace godot { -int CharString::length() const { - return _length; -} +template +_FORCE_INLINE_ bool is_str_less(const L *l_ptr, const R *r_ptr) { + while (true) { + const char32_t l = *l_ptr; + const char32_t r = *r_ptr; -const char *CharString::get_data() const { - return _data; -} + if (l == 0 && r == 0) { + return false; + } else if (l == 0) { + return true; + } else if (r == 0) { + return false; + } else if (l < r) { + return true; + } else if (l > r) { + return false; + } -CharString::CharString(CharString &&p_str) { - SWAP(_length, p_str._length); - SWAP(_data, p_str._data); -} - -void CharString::operator=(CharString &&p_str) { - SWAP(_length, p_str._length); - SWAP(_data, p_str._data); -} - -CharString::CharString(const char *str, int length) : - _data(str), _length(length) {} - -CharString::~CharString() { - if (_data != nullptr) { - memdelete_arr(_data); + l_ptr++; + r_ptr++; } } -int Char16String::length() const { - return _length; +template +bool CharStringT::operator<(const CharStringT &p_right) const { + if (length() == 0) { + return p_right.length() != 0; + } + + return is_str_less(get_data(), p_right.get_data()); } -const char16_t *Char16String::get_data() const { - return _data; +template +CharStringT &CharStringT::operator+=(T p_char) { + const int lhs_len = length(); + resize(lhs_len + 2); + + T *dst = ptrw(); + dst[lhs_len] = p_char; + dst[lhs_len + 1] = 0; + + return *this; } -Char16String::Char16String(Char16String &&p_str) { - SWAP(_length, p_str._length); - SWAP(_data, p_str._data); +template +void CharStringT::operator=(const T *p_cstr) { + copy_from(p_cstr); } -void Char16String::operator=(Char16String &&p_str) { - SWAP(_length, p_str._length); - SWAP(_data, p_str._data); -} - -Char16String::Char16String(const char16_t *str, int length) : - _data(str), _length(length) {} - -Char16String::~Char16String() { - if (_data != nullptr) { - memdelete_arr(_data); +template <> +const char *CharStringT::get_data() const { + if (size()) { + return &operator[](0); + } else { + return ""; } } -int Char32String::length() const { - return _length; -} - -const char32_t *Char32String::get_data() const { - return _data; -} - -Char32String::Char32String(Char32String &&p_str) { - SWAP(_length, p_str._length); - SWAP(_data, p_str._data); -} - -void Char32String::operator=(Char32String &&p_str) { - SWAP(_length, p_str._length); - SWAP(_data, p_str._data); -} - -Char32String::Char32String(const char32_t *str, int length) : - _data(str), _length(length) {} - -Char32String::~Char32String() { - if (_data != nullptr) { - memdelete_arr(_data); +template <> +const char16_t *CharStringT::get_data() const { + if (size()) { + return &operator[](0); + } else { + return u""; } } -int CharWideString::length() const { - return _length; -} - -const wchar_t *CharWideString::get_data() const { - return _data; -} - -CharWideString::CharWideString(CharWideString &&p_str) { - SWAP(_length, p_str._length); - SWAP(_data, p_str._data); -} - -void CharWideString::operator=(CharWideString &&p_str) { - SWAP(_length, p_str._length); - SWAP(_data, p_str._data); -} - -CharWideString::CharWideString(const wchar_t *str, int length) : - _data(str), _length(length) {} - -CharWideString::~CharWideString() { - if (_data != nullptr) { - memdelete_arr(_data); +template <> +const char32_t *CharStringT::get_data() const { + if (size()) { + return &operator[](0); + } else { + return U""; } } +template <> +const wchar_t *CharStringT::get_data() const { + if (size()) { + return &operator[](0); + } else { + return L""; + } +} + +template +void CharStringT::copy_from(const T *p_cstr) { + if (!p_cstr) { + resize(0); + return; + } + + size_t len = std::char_traits::length(p_cstr); + + if (len == 0) { + resize(0); + return; + } + + Error err = resize(++len); // include terminating null char + + ERR_FAIL_COND_MSG(err != OK, "Failed to copy C-string."); + + memcpy(ptrw(), p_cstr, len); +} + +template class CharStringT; +template class CharStringT; +template class CharStringT; +template class CharStringT; + // Custom String functions that are not part of bound API. // It's easier to have them written in C++ directly than in a Python script that generates them. @@ -228,56 +232,61 @@ String rtoss(double p_val) { CharString String::utf8() const { int length = internal::gdextension_interface_string_to_utf8_chars(_native_ptr(), nullptr, 0); int size = length + 1; - char *cstr = memnew_arr(char, size); - internal::gdextension_interface_string_to_utf8_chars(_native_ptr(), cstr, length); + CharString str; + str.resize(size); + internal::gdextension_interface_string_to_utf8_chars(_native_ptr(), str.ptrw(), length); - cstr[length] = '\0'; + str[length] = '\0'; - return CharString(cstr, length); + return str; } CharString String::ascii() const { int length = internal::gdextension_interface_string_to_latin1_chars(_native_ptr(), nullptr, 0); int size = length + 1; - char *cstr = memnew_arr(char, size); - internal::gdextension_interface_string_to_latin1_chars(_native_ptr(), cstr, length); + CharString str; + str.resize(size); + internal::gdextension_interface_string_to_latin1_chars(_native_ptr(), str.ptrw(), length); - cstr[length] = '\0'; + str[length] = '\0'; - return CharString(cstr, length); + return str; } Char16String String::utf16() const { int length = internal::gdextension_interface_string_to_utf16_chars(_native_ptr(), nullptr, 0); int size = length + 1; - char16_t *cstr = memnew_arr(char16_t, size); - internal::gdextension_interface_string_to_utf16_chars(_native_ptr(), cstr, length); + Char16String str; + str.resize(size); + internal::gdextension_interface_string_to_utf16_chars(_native_ptr(), str.ptrw(), length); - cstr[length] = '\0'; + str[length] = '\0'; - return Char16String(cstr, length); + return str; } Char32String String::utf32() const { int length = internal::gdextension_interface_string_to_utf32_chars(_native_ptr(), nullptr, 0); int size = length + 1; - char32_t *cstr = memnew_arr(char32_t, size); - internal::gdextension_interface_string_to_utf32_chars(_native_ptr(), cstr, length); + Char32String str; + str.resize(size); + internal::gdextension_interface_string_to_utf32_chars(_native_ptr(), str.ptrw(), length); - cstr[length] = '\0'; + str[length] = '\0'; - return Char32String(cstr, length); + return str; } CharWideString String::wide_string() const { int length = internal::gdextension_interface_string_to_wide_chars(_native_ptr(), nullptr, 0); int size = length + 1; - wchar_t *cstr = memnew_arr(wchar_t, size); - internal::gdextension_interface_string_to_wide_chars(_native_ptr(), cstr, length); + CharWideString str; + str.resize(size); + internal::gdextension_interface_string_to_wide_chars(_native_ptr(), str.ptrw(), length); - cstr[length] = '\0'; + str[length] = '\0'; - return CharWideString(cstr, length); + return str; } String &String::operator=(const char *p_str) { diff --git a/test/project/main.gd b/test/project/main.gd index cdd8696b..cedd5124 100644 --- a/test/project/main.gd +++ b/test/project/main.gd @@ -82,6 +82,10 @@ func _ready(): # UtilityFunctions::str() assert_equal(example.test_str_utility(), "Hello, World! The answer is 42") + # Test converting string to char* and doing comparison. + assert_equal(example.test_string_is_fourty_two("blah"), false) + assert_equal(example.test_string_is_fourty_two("fourty two"), true) + # PackedArray iterators assert_equal(example.test_vector_ops(), 105) diff --git a/test/src/example.cpp b/test/src/example.cpp index 34ee355a..fb47dd8d 100644 --- a/test/src/example.cpp +++ b/test/src/example.cpp @@ -138,6 +138,7 @@ void Example::_bind_methods() { ClassDB::bind_method(D_METHOD("test_node_argument"), &Example::test_node_argument); ClassDB::bind_method(D_METHOD("test_string_ops"), &Example::test_string_ops); ClassDB::bind_method(D_METHOD("test_str_utility"), &Example::test_str_utility); + ClassDB::bind_method(D_METHOD("test_string_is_fourty_two"), &Example::test_string_is_fourty_two); ClassDB::bind_method(D_METHOD("test_vector_ops"), &Example::test_vector_ops); ClassDB::bind_method(D_METHOD("test_bitfield", "flags"), &Example::test_bitfield); @@ -299,6 +300,10 @@ String Example::test_str_utility() const { return UtilityFunctions::str("Hello, ", "World", "! The answer is ", 42); } +bool Example::test_string_is_fourty_two(const String &p_string) const { + return strcmp(p_string.utf8().ptr(), "fourty two") == 0; +} + int Example::test_vector_ops() const { PackedInt32Array arr; arr.push_back(10); diff --git a/test/src/example.h b/test/src/example.h index a3300305..a84efedc 100644 --- a/test/src/example.h +++ b/test/src/example.h @@ -117,6 +117,7 @@ public: Example *test_node_argument(Example *p_node) const; String test_string_ops() const; String test_str_utility() const; + bool test_string_is_fourty_two(const String &p_str) const; int test_vector_ops() const; BitField test_bitfield(BitField flags); From 5dda0212f6e6b5c7921cb96accc059f2360006ff Mon Sep 17 00:00:00 2001 From: David Snopek Date: Wed, 12 Jul 2023 20:38:46 -0500 Subject: [PATCH 2/7] In generated methods, only construct the method StringName the first time (cherry picked from commit efc16b49d960413200a3b890d3db03af2e976b7f) --- binding_generator.py | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/binding_generator.py b/binding_generator.py index d04c6987..acc625db 100644 --- a/binding_generator.py +++ b/binding_generator.py @@ -1453,9 +1453,8 @@ def generate_engine_class_source(class_api, used_classes, fully_used_classes, us if is_singleton: result.append(f"{class_name} *{class_name}::get_singleton() {{") - result.append(f"\tconst StringName _gde_class_name = {class_name}::get_class_static();") result.append( - "\tstatic GDExtensionObjectPtr singleton_obj = internal::gdextension_interface_global_get_singleton(_gde_class_name._native_ptr());" + f"\tstatic GDExtensionObjectPtr singleton_obj = internal::gdextension_interface_global_get_singleton({class_name}::get_class_static()._native_ptr());" ) result.append("#ifdef DEBUG_ENABLED") result.append("\tERR_FAIL_COND_V(singleton_obj == nullptr, nullptr);") @@ -1480,10 +1479,8 @@ def generate_engine_class_source(class_api, used_classes, fully_used_classes, us result.append(method_signature + " {") # Method body. - result.append(f"\tconst StringName _gde_class_name = {class_name}::get_class_static();") - result.append(f'\tconst StringName _gde_method_name = "{method["name"]}";') result.append( - f'\tstatic GDExtensionMethodBindPtr _gde_method_bind = internal::gdextension_interface_classdb_get_method_bind(_gde_class_name._native_ptr(), _gde_method_name._native_ptr(), {method["hash"]});' + f'\tstatic GDExtensionMethodBindPtr _gde_method_bind = internal::gdextension_interface_classdb_get_method_bind({class_name}::get_class_static()._native_ptr(), StringName("{method["name"]}")._native_ptr(), {method["hash"]});' ) method_call = "\t" has_return = "return_value" in method and method["return_value"]["type"] != "void" @@ -1773,9 +1770,8 @@ def generate_utility_functions(api, output_dir): # Function body. - source.append(f'\tconst StringName _gde_function_name = "{function["name"]}";') source.append( - f'\tstatic GDExtensionPtrUtilityFunction _gde_function = internal::gdextension_interface_variant_get_ptr_utility_function(_gde_function_name._native_ptr(), {function["hash"]});' + f'\tstatic GDExtensionPtrUtilityFunction _gde_function = internal::gdextension_interface_variant_get_ptr_utility_function(StringName("{function["name"]}")._native_ptr(), {function["hash"]});' ) has_return = "return_type" in function and function["return_type"] != "void" if has_return: From e75ec636db0979b4ce352d9d587cd19996df0bdf Mon Sep 17 00:00:00 2001 From: Marc Gilleron Date: Sat, 22 Jul 2023 15:48:11 +0100 Subject: [PATCH 3/7] Don't cache `null` forever if a singleton isn't available yet # Conflicts: # binding_generator.py (cherry picked from commit 548c7586772adc76b62f5cfd13cc6fad3abc0549) --- binding_generator.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/binding_generator.py b/binding_generator.py index acc625db..6e47e7bc 100644 --- a/binding_generator.py +++ b/binding_generator.py @@ -1453,15 +1453,22 @@ def generate_engine_class_source(class_api, used_classes, fully_used_classes, us if is_singleton: result.append(f"{class_name} *{class_name}::get_singleton() {{") + # We assume multi-threaded access is OK because each assignment will assign the same value every time + result.append(f"\tstatic {class_name} *singleton = nullptr;") + result.append("\tif (unlikely(singleton == nullptr)) {") result.append( - f"\tstatic GDExtensionObjectPtr singleton_obj = internal::gdextension_interface_global_get_singleton({class_name}::get_class_static()._native_ptr());" + f"\t\tGDExtensionObjectPtr singleton_obj = internal::gdextension_interface_global_get_singleton({class_name}::get_class_static()._native_ptr());" ) result.append("#ifdef DEBUG_ENABLED") - result.append("\tERR_FAIL_COND_V(singleton_obj == nullptr, nullptr);") + result.append("\t\tERR_FAIL_COND_V(singleton_obj == nullptr, nullptr);") result.append("#endif // DEBUG_ENABLED") result.append( - f"\tstatic {class_name} *singleton = reinterpret_cast<{class_name} *>(internal::gdextension_interface_object_get_instance_binding(singleton_obj, internal::token, &{class_name}::_gde_binding_callbacks));" + f"\t\tsingleton = reinterpret_cast<{class_name} *>(internal::gdextension_interface_object_get_instance_binding(singleton_obj, internal::token, &{class_name}::_gde_binding_callbacks));" ) + result.append("#ifdef DEBUG_ENABLED") + result.append("\t\tERR_FAIL_COND_V(singleton == nullptr, nullptr);") + result.append("#endif // DEBUG_ENABLED") + result.append("\t}") result.append("\treturn singleton;") result.append("}") result.append("") From 7a9b323931334d842ae072612d4054fd22cb2c5f Mon Sep 17 00:00:00 2001 From: Adam Scott Date: Sat, 22 Jul 2023 08:46:14 -0400 Subject: [PATCH 4/7] Add platform macros (cherry picked from commit 9d9f4279ed63da1372d59bc033655eed502fe80e) --- tools/android.py | 2 ++ tools/ios.py | 2 ++ tools/javascript.py | 2 ++ tools/linux.py | 2 ++ tools/macos.py | 2 ++ tools/windows.py | 2 ++ 6 files changed, 12 insertions(+) diff --git a/tools/android.py b/tools/android.py index b9077f5d..4735345e 100644 --- a/tools/android.py +++ b/tools/android.py @@ -100,3 +100,5 @@ def generate(env): ) env.Append(CCFLAGS=arch_info["ccflags"]) env.Append(LINKFLAGS=["--target=" + arch_info["target"] + env["android_api_level"], "-march=" + arch_info["march"]]) + + env.Append(CPPDEFINES=["ANDROID_ENABLED", "UNIX_ENABLED"]) diff --git a/tools/ios.py b/tools/ios.py index 11d606b9..477e27a7 100644 --- a/tools/ios.py +++ b/tools/ios.py @@ -79,3 +79,5 @@ def generate(env): env.Append(CCFLAGS=["-isysroot", env["IOS_SDK_PATH"]]) env.Append(LINKFLAGS=["-isysroot", env["IOS_SDK_PATH"], "-F" + env["IOS_SDK_PATH"]]) + + env.Append(CPPDEFINES=["IOS_ENABLED", "UNIX_ENABLED"]) diff --git a/tools/javascript.py b/tools/javascript.py index 5a935848..42c601d2 100644 --- a/tools/javascript.py +++ b/tools/javascript.py @@ -43,3 +43,5 @@ def generate(env): env.Append(CCFLAGS=["-O0", "-g"]) elif env["target"] == "release": env.Append(CCFLAGS=["-O3"]) + + env.Append(CPPDEFINES=["WEB_ENABLED", "UNIX_ENABLED"]) diff --git a/tools/linux.py b/tools/linux.py index 099a0484..cb48ae58 100644 --- a/tools/linux.py +++ b/tools/linux.py @@ -32,3 +32,5 @@ def generate(env): elif env["arch"] == "rv64": env.Append(CCFLAGS=["-march=rv64gc"]) env.Append(LINKFLAGS=["-march=rv64gc"]) + + env.Append(CPPDEFINES=["LINUX_ENABLED", "UNIX_ENABLED"]) diff --git a/tools/macos.py b/tools/macos.py index 2e4bfc68..5718776b 100644 --- a/tools/macos.py +++ b/tools/macos.py @@ -48,3 +48,5 @@ def generate(env): "-Wl,-undefined,dynamic_lookup", ] ) + + env.Append(CPPDEFINES=["MACOS_ENABLED", "UNIX_ENABLED"]) diff --git a/tools/windows.py b/tools/windows.py index b8690c58..979b56eb 100644 --- a/tools/windows.py +++ b/tools/windows.py @@ -70,3 +70,5 @@ def generate(env): "-static-libstdc++", ] ) + + env.Append(CPPDEFINES=["WINDOWS_ENABLED"]) From 784c3dc012b566daa2a9930a243b489932bd2c61 Mon Sep 17 00:00:00 2001 From: Fabio Alessandrelli Date: Sat, 22 Jul 2023 19:54:12 +0200 Subject: [PATCH 5/7] [SCons] Add option to generate a compilation database. (cherry picked from commit 2586ad016e9b68246694644ab416bd426ed94481) --- SConstruct | 39 +++++++++++++++++++++++++++++++++------ 1 file changed, 33 insertions(+), 6 deletions(-) diff --git a/SConstruct b/SConstruct index 11ec4cfb..b028676f 100644 --- a/SConstruct +++ b/SConstruct @@ -20,14 +20,19 @@ def normalize_path(val): return val if os.path.isabs(val) else os.path.join(env.Dir("#").abspath, val) -def validate_api_file(key, val, env): +def validate_file(key, val, env): if not os.path.isfile(normalize_path(val)): - raise UserError("GDExtension API file ('%s') does not exist: %s" % (key, val)) + raise UserError("'%s' is not a file: %s" % (key, val)) -def validate_gdextension_dir(key, val, env): +def validate_dir(key, val, env): if not os.path.isdir(normalize_path(val)): - raise UserError("GDExtension directory ('%s') does not exist: %s" % (key, val)) + raise UserError("'%s' is not a directory: %s" % (key, val)) + + +def validate_parent_dir(key, val, env): + if not os.path.isdir(normalize_path(os.path.dirname(val))): + raise UserError("'%s' is not a directory: %s" % (key, os.path.dirname(val))) def get_gdextension_dir(env): @@ -115,7 +120,7 @@ opts.Add( key="gdextension_dir", help="Path to a custom directory containing GDExtension interface header and API JSON file", default=env.get("gdextension_dir", None), - validator=validate_gdextension_dir, + validator=validate_dir, ) ) opts.Add( @@ -123,7 +128,7 @@ opts.Add( key="custom_api_file", help="Path to a custom GDExtension API JSON file (takes precedence over `gdextension_dir`)", default=env.get("custom_api_file", None), - validator=validate_api_file, + validator=validate_file, ) ) opts.Add( @@ -151,6 +156,23 @@ opts.Add( ) ) +# compiledb +opts.Add( + BoolVariable( + key="compiledb", + help="Generate compilation DB (`compile_commands.json`) for external tools", + default=env.get("compiledb", False), + ) +) +opts.Add( + PathVariable( + key="compiledb_file", + help="Path to a custom `compile_commands.json` file", + default=env.get("compiledb_file", "compile_commands.json"), + validator=validate_parent_dir, + ) +) + # Add platform options tools = {} for pl in platforms: @@ -242,6 +264,11 @@ else: if env["precision"] == "double": env.Append(CPPDEFINES=["REAL_T_IS_DOUBLE"]) +# compile_commands.json +if env.get("compiledb", False): + env.Tool("compilation_db") + env.Alias("compiledb", env.CompilationDatabase(normalize_path(env["compiledb_file"]))) + # Generate bindings env.Append(BUILDERS={"GenerateBindings": Builder(action=scons_generate_bindings, emitter=scons_emit_files)}) From 6fa6b8b1782fc8d1d2613edb7c70b1bf59f17f20 Mon Sep 17 00:00:00 2001 From: Fabio Alessandrelli Date: Sun, 23 Jul 2023 22:41:55 +0200 Subject: [PATCH 6/7] [SCons] Merge OSXCross tools into platofrm ones (cherry picked from commit 6d195137fe21d6169bc43a928141c006efc7c1f2) --- tools/ios.py | 35 +++++++++++++++++++++++++++++------ tools/ios_osxcross.py | 26 -------------------------- tools/macos.py | 30 +++++++++++++++++++++++++----- tools/macos_osxcross.py | 28 ---------------------------- 4 files changed, 54 insertions(+), 65 deletions(-) delete mode 100644 tools/ios_osxcross.py delete mode 100644 tools/macos_osxcross.py diff --git a/tools/ios.py b/tools/ios.py index 477e27a7..e387f426 100644 --- a/tools/ios.py +++ b/tools/ios.py @@ -1,7 +1,6 @@ import os import sys import subprocess -import ios_osxcross from SCons.Variables import * if sys.version_info < (3,): @@ -16,6 +15,10 @@ else: return codecs.utf_8_decode(x)[0] +def has_ios_osxcross(): + return "OSXCROSS_IOS" in os.environ + + def options(opts): opts.Add(BoolVariable("ios_simulator", "Target iOS Simulator", False)) opts.Add("ios_min_version", "Target minimum iphoneos/iphonesimulator version", "10.0") @@ -25,17 +28,18 @@ def options(opts): "/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain", ) opts.Add("IOS_SDK_PATH", "Path to the iOS SDK", "") - ios_osxcross.options(opts) + + if has_ios_osxcross(): + opts.Add("ios_triple", "Triple for ios toolchain", "") def exists(env): - return sys.platform == "darwin" or ios_osxcross.exists(env) + return sys.platform == "darwin" or has_ios_osxcross() def generate(env): if env["arch"] not in ("universal", "arm64", "x86_64"): - print("Only universal, arm64, and x86_64 are supported on iOS. Exiting.") - Exit() + raise ValueError("Only universal, arm64, and x86_64 are supported on iOS. Exiting.") if env["ios_simulator"]: sdk_name = "iphonesimulator" @@ -64,7 +68,26 @@ def generate(env): env["ENV"]["PATH"] = env["IOS_TOOLCHAIN_PATH"] + "/Developer/usr/bin/:" + env["ENV"]["PATH"] else: - ios_osxcross.generate(env) + # OSXCross + compiler_path = "$IOS_TOOLCHAIN_PATH/usr/bin/${ios_triple}" + env["CC"] = compiler_path + "clang" + env["CXX"] = compiler_path + "clang++" + env["AR"] = compiler_path + "ar" + env["RANLIB"] = compiler_path + "ranlib" + env["SHLIBSUFFIX"] = ".dylib" + + env.Prepend( + CPPPATH=[ + "$IOS_SDK_PATH/usr/include", + "$IOS_SDK_PATH/System/Library/Frameworks/AudioUnit.framework/Headers", + ] + ) + + env.Append(CCFLAGS=["-stdlib=libc++"]) + + binpath = os.path.join(env["IOS_TOOLCHAIN_PATH"], "usr", "bin") + if binpath not in env["ENV"]["PATH"]: + env.PrependENVPath("PATH", binpath) if env["arch"] == "universal": if env["ios_simulator"]: diff --git a/tools/ios_osxcross.py b/tools/ios_osxcross.py deleted file mode 100644 index 21a3f5e4..00000000 --- a/tools/ios_osxcross.py +++ /dev/null @@ -1,26 +0,0 @@ -import os - - -def options(opts): - opts.Add("ios_triple", "Triple for ios toolchain", "") - - -def exists(env): - return "OSXCROSS_IOS" in os.environ - - -def generate(env): - compiler_path = "$IOS_TOOLCHAIN_PATH/usr/bin/${ios_triple}" - env["CC"] = compiler_path + "clang" - env["CXX"] = compiler_path + "clang++" - env["AR"] = compiler_path + "ar" - env["RANLIB"] = compiler_path + "ranlib" - env["SHLIBSUFFIX"] = ".dylib" - - env.Prepend( - CPPPATH=[ - "$IOS_SDK_PATH/usr/include", - "$IOS_SDK_PATH/System/Library/Frameworks/AudioUnit.framework/Headers", - ] - ) - env.Append(CCFLAGS=["-stdlib=libc++"]) diff --git a/tools/macos.py b/tools/macos.py index 5718776b..34a755ab 100644 --- a/tools/macos.py +++ b/tools/macos.py @@ -1,16 +1,20 @@ import os import sys -import macos_osxcross + + +def has_osxcross(): + return "OSXCROSS_ROOT" in os.environ def options(opts): opts.Add("macos_deployment_target", "macOS deployment target", "default") opts.Add("macos_sdk_path", "macOS SDK path", "") - macos_osxcross.options(opts) + if has_osxcross(): + opts.Add("osxcross_sdk", "OSXCross SDK version", "darwin16") def exists(env): - return sys.platform == "darwin" or macos_osxcross.exists(env) + return sys.platform == "darwin" or has_osxcross() def generate(env): @@ -23,9 +27,25 @@ def generate(env): env["CXX"] = "clang++" env["CC"] = "clang" else: - # Use osxcross - macos_osxcross.generate(env) + # OSXCross + root = os.environ.get("OSXCROSS_ROOT", "") + if env["arch"] == "arm64": + basecmd = root + "/target/bin/arm64-apple-" + env["osxcross_sdk"] + "-" + else: + basecmd = root + "/target/bin/x86_64-apple-" + env["osxcross_sdk"] + "-" + env["CC"] = basecmd + "clang" + env["CXX"] = basecmd + "clang++" + env["AR"] = basecmd + "ar" + env["RANLIB"] = basecmd + "ranlib" + env["AS"] = basecmd + "as" + + binpath = os.path.join(root, "target", "bin") + if binpath not in env["ENV"]["PATH"]: + # Add OSXCROSS bin folder to PATH (required for linking). + env.PrependENVPath("PATH", binpath) + + # Common flags if env["arch"] == "universal": env.Append(LINKFLAGS=["-arch", "x86_64", "-arch", "arm64"]) env.Append(CCFLAGS=["-arch", "x86_64", "-arch", "arm64"]) diff --git a/tools/macos_osxcross.py b/tools/macos_osxcross.py deleted file mode 100644 index f11166d1..00000000 --- a/tools/macos_osxcross.py +++ /dev/null @@ -1,28 +0,0 @@ -import os - - -def options(opts): - opts.Add("osxcross_sdk", "OSXCross SDK version", "darwin16") - - -def exists(env): - return "OSXCROSS_ROOT" in os.environ - - -def generate(env): - root = os.environ.get("OSXCROSS_ROOT", "") - if env["arch"] == "arm64": - basecmd = root + "/target/bin/arm64-apple-" + env["osxcross_sdk"] + "-" - else: - basecmd = root + "/target/bin/x86_64-apple-" + env["osxcross_sdk"] + "-" - - env["CC"] = basecmd + "clang" - env["CXX"] = basecmd + "clang++" - env["AR"] = basecmd + "ar" - env["RANLIB"] = basecmd + "ranlib" - env["AS"] = basecmd + "as" - - binpath = os.path.join(root, "target", "bin") - if binpath not in env["ENV"]["PATH"]: - # Add OSXCROSS bin folder to PATH (required for linking). - env["ENV"]["PATH"] = "%s:%s" % (binpath, env["ENV"]["PATH"]) From 4fb9af7fb2ee17112bb8cdc565a3c7d8caa85861 Mon Sep 17 00:00:00 2001 From: Feiyun Wang Date: Sun, 2 Apr 2023 22:46:40 +0800 Subject: [PATCH 7/7] Statically link mingw/msvc runtime libraries on Windows Co-authored-by: David Snopek (cherry picked from commit a745c2ac478157586120e37e2e4ba19a206c4dd3) --- tools/targets.py | 12 ++++++------ tools/windows.py | 38 ++++++++++++++++++++++++++++---------- 2 files changed, 34 insertions(+), 16 deletions(-) diff --git a/tools/targets.py b/tools/targets.py index e9857dd1..5f8e7688 100644 --- a/tools/targets.py +++ b/tools/targets.py @@ -60,17 +60,17 @@ def generate(env): env.Append(CCFLAGS=["/Zi", "/FS"]) env.Append(LINKFLAGS=["/DEBUG:FULL"]) - if env["optimize"] == "speed" or env["optimize"] == "speed_trace": + if env["optimize"] == "speed": env.Append(CCFLAGS=["/O2"]) env.Append(LINKFLAGS=["/OPT:REF"]) + elif env["optimize"] == "speed_trace": + env.Append(CCFLAGS=["/O2"]) + env.Append(LINKFLAGS=["/OPT:REF", "/OPT:NOICF"]) elif env["optimize"] == "size": env.Append(CCFLAGS=["/O1"]) env.Append(LINKFLAGS=["/OPT:REF"]) - - if env["optimize"] == "debug" or env["optimize"] == "none": - env.Append(CCFLAGS=["/MDd", "/Od"]) - else: - env.Append(CCFLAGS=["/MD"]) + elif env["optimize"] == "debug" or env["optimize"] == "none": + env.Append(CCFLAGS=["/Od"]) else: if env["debug_symbols"]: diff --git a/tools/windows.py b/tools/windows.py index 979b56eb..e156aefb 100644 --- a/tools/windows.py +++ b/tools/windows.py @@ -9,6 +9,7 @@ from SCons.Variables import * def options(opts): opts.Add(BoolVariable("use_mingw", "Use the MinGW compiler instead of MSVC - only effective on Windows", False)) opts.Add(BoolVariable("use_clang_cl", "Use the clang driver instead of MSVC - only effective on Windows", False)) + opts.Add(BoolVariable("use_static_cpp", "Link MinGW/MSVC C++ runtime libraries statically", True)) def exists(env): @@ -37,6 +38,11 @@ def generate(env): env["CC"] = "clang-cl" env["CXX"] = "clang-cl" + if env["use_static_cpp"]: + env.Append(CCFLAGS=["/MT"]) + else: + env.Append(CCFLAGS=["/MD"]) + elif sys.platform == "win32" or sys.platform == "msys": env["use_mingw"] = True mingw.generate(env) @@ -45,6 +51,18 @@ def generate(env): env["SHLIBPREFIX"] = "" # Want dll suffix env["SHLIBSUFFIX"] = ".dll" + + env.Append(CCFLAGS=["-Wwrite-strings"]) + env.Append(LINKFLAGS=["-Wl,--no-undefined"]) + if env["use_static_cpp"]: + env.Append( + LINKFLAGS=[ + "-static", + "-static-libgcc", + "-static-libstdc++", + ] + ) + # Long line hack. Use custom spawn, quick AR append (to avoid files with the same names to override each other). my_spawn.configure(env) @@ -60,15 +78,15 @@ def generate(env): # Want dll suffix env["SHLIBSUFFIX"] = ".dll" - # These options are for a release build even using target=debug - env.Append(CCFLAGS=["-O3", "-Wwrite-strings"]) - env.Append( - LINKFLAGS=[ - "--static", - "-Wl,--no-undefined", - "-static-libgcc", - "-static-libstdc++", - ] - ) + env.Append(CCFLAGS=["-Wwrite-strings"]) + env.Append(LINKFLAGS=["-Wl,--no-undefined"]) + if env["use_static_cpp"]: + env.Append( + LINKFLAGS=[ + "-static", + "-static-libgcc", + "-static-libstdc++", + ] + ) env.Append(CPPDEFINES=["WINDOWS_ENABLED"])