diff --git a/.github/actions/godot-cache-restore/action.yml b/.github/actions/godot-cache-restore/action.yml index 5df577656..e38df0bb6 100644 --- a/.github/actions/godot-cache-restore/action.yml +++ b/.github/actions/godot-cache-restore/action.yml @@ -5,17 +5,18 @@ inputs: description: The cache base name (job name by default). default: "${{github.job}}" scons-cache: - description: The scons cache path. + description: The SCons cache path. default: "${{github.workspace}}/.scons-cache/" runs: - using: "composite" + using: composite steps: - - name: Restore .scons_cache directory - uses: actions/cache/restore@v3 + - name: Restore SCons cache directory + uses: actions/cache/restore@v4 with: path: ${{inputs.scons-cache}} - key: ${{inputs.cache-name}}-${{env.GODOT_BASE_BRANCH}}-${{github.ref}}-${{github.sha}} + key: ${{inputs.cache-name}}-${{env.BLAZIUM_BASE_BRANCH}}-${{github.ref}}-${{github.sha}} restore-keys: | - ${{inputs.cache-name}}-${{env.GODOT_BASE_BRANCH}}-${{github.ref}}-${{github.sha}} - ${{inputs.cache-name}}-${{env.GODOT_BASE_BRANCH}}-${{github.ref}} - ${{inputs.cache-name}}-${{env.GODOT_BASE_BRANCH}} + ${{inputs.cache-name}}-${{env.BLAZIUM_BASE_BRANCH}}-${{github.ref}}-${{github.sha}} + ${{inputs.cache-name}}-${{env.BLAZIUM_BASE_BRANCH}}-${{github.ref}} + ${{inputs.cache-name}}-${{env.BLAZIUM_BASE_BRANCH}}-refs/heads/${{env.BLAZIUM_BASE_BRANCH}} + ${{inputs.cache-name}}-${{env.BLAZIUM_BASE_BRANCH}} diff --git a/.github/actions/godot-cache-save/action.yml b/.github/actions/godot-cache-save/action.yml index b7cbf91f9..061da6a25 100644 --- a/.github/actions/godot-cache-save/action.yml +++ b/.github/actions/godot-cache-save/action.yml @@ -8,10 +8,10 @@ inputs: description: The SCons cache path. default: "${{github.workspace}}/.scons-cache/" runs: - using: "composite" + using: composite steps: - name: Save SCons cache directory uses: actions/cache/save@v4 with: path: ${{inputs.scons-cache}} - key: ${{inputs.cache-name}}-${{env.GODOT_BASE_BRANCH}}-${{github.ref}}-${{github.sha}} + key: ${{inputs.cache-name}}-${{env.BLAZIUM_BASE_BRANCH}}-${{github.ref}}-${{github.sha}} diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 13b7243e0..a30acbc30 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -3,9 +3,12 @@ on: [push, pull_request] env: # Only used for the cache key. Increment version to force clean build. - GODOT_BASE_BRANCH: master + BLAZIUM_BASE_BRANCH: blazium-dev # Used to select the version of Godot to run the tests with. - GODOT_TEST_VERSION: master + BLAZIUM_TEST_VERSION: 0.1.184 + # Use UTF-8 on Linux. + LANG: en_US.UTF-8 + LC_ALL: en_US.UTF-8 concurrency: group: ci-${{github.actor}}-${{github.head_ref || github.run_number}}-${{github.ref}} @@ -91,7 +94,6 @@ jobs: env: SCONS_CACHE: ${{ github.workspace }}/.scons-cache/ EM_VERSION: 3.1.39 - EM_CACHE_FOLDER: "emsdk-cache" steps: - name: Checkout @@ -108,10 +110,10 @@ jobs: - name: Set up Python (for SCons) uses: actions/setup-python@v5 with: - python-version: '3.x' + python-version: 3.x - name: Android dependencies - if: ${{ matrix.platform == 'android' }} + if: matrix.platform == 'android' uses: nttld/setup-ndk@v1 with: ndk-version: r23c @@ -122,7 +124,7 @@ jobs: uses: mymindstorm/setup-emsdk@v14 with: version: ${{env.EM_VERSION}} - actions-cache-folder: ${{env.EM_CACHE_FOLDER}} + no-cache: true - name: Setup MinGW for Windows/MinGW build if: ${{ matrix.platform == 'windows' && matrix.flags == 'use_mingw=yes' }} @@ -161,10 +163,10 @@ jobs: - name: Download latest Godot artifacts uses: dsnopek/action-download-artifact@1322f74e2dac9feed2ee76a32d9ae1ca3b4cf4e9 - if: ${{ matrix.run-tests && env.GODOT_TEST_VERSION == 'master' }} + if: matrix.run-tests && env.BLAZIUM_TEST_VERSION == 'blazium-dev' with: - repo: godotengine/godot - branch: master + repo: blazium-engine/blazium + branch: blazium-dev event: push workflow: linux_builds.yml workflow_conclusion: success @@ -175,59 +177,35 @@ jobs: path: godot-artifacts - name: Prepare Godot artifacts for testing - if: ${{ matrix.run-tests && env.GODOT_TEST_VERSION == 'master' }} + if: matrix.run-tests && env.BLAZIUM_TEST_VERSION == 'blazium-dev' run: | chmod +x ./godot-artifacts/godot.linuxbsd.editor.x86_64.mono - echo "GODOT=$(pwd)/godot-artifacts/godot.linuxbsd.editor.x86_64.mono" >> $GITHUB_ENV + echo "BLAZIUM=$(pwd)/godot-artifacts/godot.linuxbsd.editor.x86_64.mono" >> $GITHUB_ENV - name: Download requested Godot version for testing - if: ${{ matrix.run-tests && env.GODOT_TEST_VERSION != 'master' }} + if: matrix.run-tests && env.BLAZIUM_TEST_VERSION != 'blazium-dev' run: | - wget "https://github.com/godotengine/godot-builds/releases/download/${GODOT_TEST_VERSION}/Godot_v${GODOT_TEST_VERSION}_linux.x86_64.zip" -O Godot.zip - unzip -a Godot.zip - chmod +x "Godot_v${GODOT_TEST_VERSION}_linux.x86_64" - echo "GODOT=$(pwd)/Godot_v${GODOT_TEST_VERSION}_linux.x86_64" >> $GITHUB_ENV + wget "https://cdn.blazium.app/nightly/${BLAZIUM_TEST_VERSION}/BlaziumEditor_v${BLAZIUM_TEST_VERSION}_linux.x86_64.zip" -O Blazium.zip + unzip -a Blazium.zip + chmod +x "BlaziumEditor_v${BLAZIUM_TEST_VERSION}_linux.x86_64" + echo "BLAZIUM=$(pwd)/BlaziumEditor_v${BLAZIUM_TEST_VERSION}_linux.x86_64" >> $GITHUB_ENV - name: Run tests - if: ${{ matrix.run-tests }} + if: matrix.run-tests run: | - $GODOT --headless --version + $BLAZIUM --headless --version cd test # Need to run the editor so .godot is generated... but it crashes! Ignore that :-) - (cd project && (timeout 30 $GODOT --import --headless >/dev/null 2>&1 || true)) + (cd project && (timeout 30 $BLAZIUM --import --headless >/dev/null 2>&1 || true)) ./run-tests.sh - name: Upload artifact - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: ${{ matrix.artifact-name }} path: ${{ matrix.artifact-path }} if-no-files-found: error - linux-cmake: - name: 🐧 Build (Linux, GCC, CMake) - runs-on: ubuntu-20.04 - steps: - - name: Checkout - uses: actions/checkout@v4 - with: - submodules: recursive - - - name: Install dependencies - run: | - sudo apt-get update -qq - sudo apt-get install -qqq build-essential pkg-config cmake - - - name: Build godot-cpp - run: | - cmake -DCMAKE_BUILD_TYPE=Release . - make -j $(nproc) VERBOSE=1 - - - name: Build test GDExtension library - run: | - cd test && cmake -DCMAKE_BUILD_TYPE=Release -DGODOT_HEADERS_PATH="../godot-headers" -DCPP_BINDINGS_PATH=".." . - make -j $(nproc) VERBOSE=1 - linux-cmake-ninja: name: 🐧 Build (Linux, GCC, CMake Ninja) runs-on: ubuntu-20.04 @@ -242,15 +220,12 @@ jobs: sudo apt-get update -qq sudo apt-get install -qqq build-essential pkg-config cmake ninja-build - - name: Build godot-cpp - run: | - cmake -DCMAKE_BUILD_TYPE=Release -GNinja . - cmake --build . -j $(nproc) --verbose - - name: Build test GDExtension library run: | - cd test && cmake -DCMAKE_BUILD_TYPE=Release -DGODOT_HEADERS_PATH="../godot-headers" -DCPP_BINDINGS_PATH=".." -GNinja . - cmake --build . -j $(nproc) --verbose + mkdir cmake-build + cd cmake-build + cmake ../ -DTEST_TARGET=template_release + cmake --build . --verbose -j $(nproc) -t godot-cpp-test --config Release windows-msvc-cmake: name: 🏁 Build (Windows, MSVC, CMake) @@ -261,12 +236,9 @@ jobs: with: submodules: recursive - - name: Build godot-cpp - run: | - cmake -DCMAKE_BUILD_TYPE=Release -G"Visual Studio 16 2019" . - cmake --build . --verbose --config Release - - name: Build test GDExtension library run: | - cd test && cmake -DCMAKE_BUILD_TYPE=Release -DGODOT_HEADERS_PATH="../godot-headers" -DCPP_BINDINGS_PATH=".." -G"Visual Studio 16 2019" . - cmake --build . --verbose --config Release + mkdir cmake-build + cd cmake-build + cmake ../ -DTEST_TARGET=template_release + cmake --build . --verbose -t godot-cpp-test --config Release diff --git a/.github/workflows/runner.yml b/.github/workflows/runner.yml new file mode 100644 index 000000000..a2e4f91b8 --- /dev/null +++ b/.github/workflows/runner.yml @@ -0,0 +1,21 @@ +name: 🔗 GHA +on: [push, pull_request, merge_group] + +concurrency: + group: ci-${{ github.actor }}-${{ github.head_ref || github.run_number }}-${{ github.ref }}-runner + cancel-in-progress: true + +jobs: + # First stage: Only static checks, fast and prevent expensive builds from running. + + static-checks: + if: '!vars.DISABLE_GODOT_CI' + name: 📊 Static Checks + uses: ./.github/workflows/static_checks.yml + + # Second stage: Run all the builds and some of the tests. + + ci: + name: 🛠️ Continuous Integration + needs: static-checks + uses: ./.github/workflows/ci.yml diff --git a/.gitignore b/.gitignore index ee9a3f756..9bb08ed34 100644 --- a/.gitignore +++ b/.gitignore @@ -198,4 +198,7 @@ venv # Clion Configuration .idea/ -cmake-build-* +cmake-build*/ + +# CMake related +CMakeUserPresets.json diff --git a/CMakeLists.txt b/CMakeLists.txt index ff77368ba..208fae82b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,24 +1,98 @@ -cmake_minimum_required(VERSION 3.13) -project(godot-cpp LANGUAGES CXX) - -# Configure CMake -# https://discourse.cmake.org/t/how-do-i-remove-compile-options-from-target/5965 -# https://stackoverflow.com/questions/74426638/how-to-remove-rtc1-from-specific-target-or-file-in-cmake -if(${CMAKE_CXX_COMPILER_ID} STREQUAL MSVC) - if(NOT CMAKE_BUILD_TYPE MATCHES Debug) - STRING(REGEX REPLACE "/RTC(su|[1su])" "" CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}") - string(REPLACE "/RTC1" "" CMAKE_CXX_FLAGS_DEBUG ${CMAKE_CXX_FLAGS_DEBUG}) - endif () -endif () +cmake_minimum_required(VERSION 3.17) + +#[=======================================================================[.rst: + +CMake Version requirements +-------------------------- + +To enable use of the emscripten emsdk hack for pseudo shared library support +without polluting options for consumers we need to use the +CMAKE_PROJECT__INCLUDE which was introduced in version 3.17 + +Scons Compatibility +------------------- + +As we are attempting to maintain feature parity, and ease of maintenance, these +CMake scripts are built to resemble the SCons build system. -include( ${PROJECT_SOURCE_DIR}/cmake/godotcpp.cmake ) +The file structure and file content are made to match, if not in content then +in spirit. The closer the two build systems look the easier they will be to +maintain. -# I know this doesn't look like a typical CMakeLists.txt, but as we are -# attempting mostly feature parity with SCons, and easy maintenance, the closer -# the two build systems look the easier they will be to keep in lockstep. +Where the SCons additional scripts in the tools directory, The CMake scripts +are in the cmake directory. -# The typical target definitions are in ${PROJECT_SOURCE_DIR}/cmake/godotcpp.cmake +For example, the tools/godotcpp.py is sourced into SCons, and the 'options' +function is run. +.. highlight:: python + + cpp_tool = Tool("godotcpp", toolpath=["tools"]) + cpp_tool.options(opts, env) + + +The CMake equivalent is below. +]=======================================================================] + +include( cmake/godotcpp.cmake ) godotcpp_options() +#[=======================================================================[.rst: + +Configurations +-------------- + +There are two build main configurations, 'Debug' and 'Release', these are not +related to godot's DEBUG_FEATURES flag. Build configurations change the default +compiler and linker flags present when building the library, things like debug +symbols, optimization. + +The Scons build scripts don't have this concept, you can think of it like the +SCons solution has a single default configuration. In both cases overriding the +defaults is controlled by options on the command line, or in preset files. + +Because of this added configuration and that it can be undefined, it becomes +important to set a default, considering the SCons solution that does not enable +debug symbols by default, it seemed appropriate to set the default to 'Release' +if unspecified. This can always be overridden like below. + +.. highlight:: shell + + cmake -DCMAKE_BUILD_TYPE:STRING=Debug + +.. caution:: + +A complication arises from `Multi-Config Generators`_ that cannot have +their configuration set at configure time. This means that the configuration +must be set on the build command. This is especially important for Visual +Studio Generators which default to 'Debug' + +.. highlight:: shell + + cmake --build . --config Release + +.. _Multi-Config Generators:https://cmake.org/cmake/help/latest/prop_gbl/GENERATOR_IS_MULTI_CONFIG.html +]=======================================================================] +get_property( IS_MULTI_CONFIG GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG ) +if( NOT IS_MULTI_CONFIG AND NOT CMAKE_BUILD_TYPE ) + if( GODOT_DEV_BUILD ) + set( CMAKE_BUILD_TYPE Debug ) + else () + set( CMAKE_BUILD_TYPE Release ) + endif () +endif () + +#[[ Python is required for code generation ]] +find_package(Python3 3.4 REQUIRED) # pathlib should be present + +# Define our project. +project( godot-cpp + VERSION 4.3 + DESCRIPTION "C++ bindings for the Godot Engine's GDExtensions API." + HOMEPAGE_URL "https://github.com/blazium-engine/godot-cpp" + LANGUAGES CXX) + godotcpp_generate() + +# Test Example +add_subdirectory( test ) diff --git a/binding_generator.py b/binding_generator.py index 75e56fde1..71afe047e 100644 --- a/binding_generator.py +++ b/binding_generator.py @@ -542,8 +542,14 @@ def generate_builtin_bindings(api, output_dir, build_config): builtin_header.append("") + includes = [] for builtin in builtin_classes: - builtin_header.append(f"#include ") + includes.append(f"godot_cpp/variant/{camel_to_snake(builtin)}.hpp") + + includes.sort() + + for include in includes: + builtin_header.append(f"#include <{include}>") builtin_header.append("") @@ -599,11 +605,10 @@ def generate_builtin_class_vararg_method_implements_header(builtin_classes): continue result += make_varargs_template( - method, "is_static" in method and method["is_static"], class_name, False, False, True + method, "is_static" in method and method["is_static"], class_name, False, True ) result.append("") - result.append("") result.append(f"#endif // ! {header_guard}") return "\n".join(result) @@ -628,38 +633,53 @@ def generate_builtin_class_header(builtin_api, size, used_classes, fully_used_cl # Special cases. if class_name == "String": + result.append("#include ") result.append("#include ") result.append("#include ") - result.append("#include ") + result.append("") if class_name == "PackedStringArray": result.append("#include ") + result.append("") if class_name == "PackedColorArray": result.append("#include ") + result.append("") if class_name == "PackedVector2Array": result.append("#include ") + result.append("") if class_name == "PackedVector3Array": result.append("#include ") + result.append("") if class_name == "PackedVector4Array": result.append("#include ") + result.append("") if is_packed_array(class_name): result.append("#include ") result.append("#include ") + result.append("") if class_name == "Array": result.append("#include ") + result.append("") if class_name == "Callable": result.append("#include ") - - for include in fully_used_classes: - if include == "TypedArray": - result.append("#include ") - else: - result.append(f"#include ") + result.append("") if len(fully_used_classes) > 0: + includes = [] + for include in fully_used_classes: + if include == "TypedArray": + includes.append("godot_cpp/variant/typed_array.hpp") + else: + includes.append(f"godot_cpp/{get_include_path(include)}") + + includes.sort() + + for include in includes: + result.append(f"#include <{include}>") + result.append("") result.append("#include ") @@ -737,7 +757,7 @@ def generate_builtin_class_header(builtin_api, size, used_classes, fully_used_cl result.append("public:") result.append( - f"\t_FORCE_INLINE_ GDExtensionTypePtr _native_ptr() const {{ return const_cast(&opaque); }}" + f"\t_FORCE_INLINE_ GDExtensionTypePtr _native_ptr() const {{ return const_cast(&opaque); }}" ) copy_constructor_index = -1 @@ -918,7 +938,7 @@ def generate_builtin_class_header(builtin_api, size, used_classes, fully_used_cl result.append(f"\tconst {return_type} *ptr() const;") result.append(f"\t{return_type} *ptrw();") iterators = """ - struct Iterator { + struct Iterator { _FORCE_INLINE_ $TYPE &operator*() const { return *elem_ptr; } @@ -980,19 +1000,17 @@ def generate_builtin_class_header(builtin_api, size, used_classes, fully_used_cl } _FORCE_INLINE_ ConstIterator end() const { return ConstIterator(ptr() + size()); - } -""" + }""" result.append(iterators.replace("$TYPE", return_type)) init_list = """ - _FORCE_INLINE_ $CLASS(std::initializer_list<$TYPE> p_init) { + _FORCE_INLINE_ $CLASS(std::initializer_list<$TYPE> p_init) { ERR_FAIL_COND(resize(p_init.size()) != 0); size_t i = 0; for (const $TYPE &element : p_init) { set(i++, element); } - } -""" + }""" result.append(init_list.replace("$TYPE", return_type).replace("$CLASS", class_name)) if class_name == "Array": @@ -1031,7 +1049,9 @@ def generate_builtin_class_header(builtin_api, size, used_classes, fully_used_cl result.append("") result.append("} // namespace godot") + result.append("") result.append(f"#endif // ! {header_guard}") + result.append("") return "\n".join(result) @@ -1045,7 +1065,6 @@ def generate_builtin_class_source(builtin_api, size, used_classes, fully_used_cl add_header(f"{snake_class_name}.cpp", result) - result.append("") result.append(f"#include ") result.append("") result.append("#include ") @@ -1054,10 +1073,16 @@ def generate_builtin_class_source(builtin_api, size, used_classes, fully_used_cl result.append("") # Only used since the "fully used" is included in header already. - for include in used_classes: - result.append(f"#include ") - if len(used_classes) > 0: + includes = [] + for included in used_classes: + includes.append(f"godot_cpp/{get_include_path(included)}") + + includes.sort() + + for included in includes: + result.append(f"#include <{included}>") + result.append("") result.append("#include ") @@ -1307,7 +1332,7 @@ def generate_builtin_class_source(builtin_api, size, used_classes, fully_used_cl f'{correct_type(operator["return_type"])} {class_name}::operator{get_operator_cpp_name(operator["name"])}() const {{' ) result.append( - f'\treturn internal::_call_builtin_operator_ptr<{get_gdextension_type(correct_type(operator["return_type"]))}>(_method_bindings.operator_{get_operator_id_name(operator["name"])}, (GDExtensionConstTypePtr)&opaque, (GDExtensionConstTypePtr)nullptr);' + f'\treturn internal::_call_builtin_operator_ptr<{get_gdextension_type(correct_type(operator["return_type"]))}>(_method_bindings.operator_{get_operator_id_name(operator["name"])}, (GDExtensionConstTypePtr)&opaque, (GDExtensionConstTypePtr) nullptr);' ) result.append("}") result.append("") @@ -1343,6 +1368,7 @@ def generate_builtin_class_source(builtin_api, size, used_classes, fully_used_cl result.append("") result.append("} //namespace godot") + result.append("") return "\n".join(result) @@ -1518,11 +1544,18 @@ def generate_engine_classes_bindings(api, output_dir, use_template_get_node): result.append("") - for included in used_classes: - result.append(f"#include ") + if len(used_classes) > 0: + includes = [] + for included in used_classes: + includes.append(f"godot_cpp/{get_include_path(included)}") - if len(used_classes) == 0: + includes.sort() + + for include in includes: + result.append(f"#include <{include}>") + else: result.append("#include ") + result.append("") result.append("namespace godot {") @@ -1562,16 +1595,23 @@ def generate_engine_class_header(class_api, used_classes, fully_used_classes, us result.append("") - for included in fully_used_classes: - if included == "TypedArray": - result.append("#include ") - else: - result.append(f"#include ") + if len(fully_used_classes) > 0: + includes = [] + for included in fully_used_classes: + if included == "TypedArray": + includes.append("godot_cpp/variant/typed_array.hpp") + else: + includes.append(f"godot_cpp/{get_include_path(included)}") + + includes.sort() + + for include in includes: + result.append(f"#include <{include}>") + + result.append("") if class_name == "EditorPlugin": result.append("#include ") - - if len(fully_used_classes) > 0: result.append("") if class_name != "Object" and class_name != "ClassDBSingleton": @@ -1610,7 +1650,6 @@ def generate_engine_class_header(class_api, used_classes, fully_used_classes, us result.append("") result.append("public:") - result.append("") if "enums" in class_api: for enum_api in class_api["enums"]: @@ -1643,6 +1682,10 @@ def generate_engine_class_header(class_api, used_classes, fully_used_classes, us vararg = "is_vararg" in method and method["is_vararg"] + if vararg: + result.append("") + result.append("private:") + method_signature = "\t" method_signature += make_signature( class_name, method, for_header=True, use_template_get_node=use_template_get_node @@ -1650,6 +1693,8 @@ def generate_engine_class_header(class_api, used_classes, fully_used_classes, us result.append(method_signature + ";") if vararg: + result.append("") + result.append("public:") # Add templated version. result += make_varargs_template(method) @@ -1664,6 +1709,8 @@ def generate_engine_class_header(class_api, used_classes, fully_used_classes, us ) result.append(method_signature + ";") + result.append("") + result.append("protected:") # T is the custom class we want to register (from which the call initiates, going up the inheritance chain), # B is its base class (can be a custom class too, that's why we pass it). @@ -1680,7 +1727,7 @@ def generate_engine_class_header(class_api, used_classes, fully_used_classes, us # If the method is different from the base class, it means T overrides it, so it needs to be bound. # Note that with an `if constexpr`, the code inside the `if` will not even be compiled if the # condition returns false (in such cases it can't compile due to ambiguity). - f"\t\tif constexpr (!std::is_same_v) {{" + f"\t\tif constexpr (!std::is_same_v) {{" ) result.append(f"\t\t\tBIND_VIRTUAL_METHOD(T, {method_name});") result.append("\t\t}") @@ -1708,7 +1755,7 @@ def generate_engine_class_header(class_api, used_classes, fully_used_classes, us if class_name == "WorkerThreadPool": result.append("\tenum {") - result.append("\tINVALID_TASK_ID = -1") + result.append("\t\tINVALID_TASK_ID = -1") result.append("\t};") result.append("\ttypedef int64_t TaskID;") result.append("\ttypedef int64_t GroupID;") @@ -1720,8 +1767,6 @@ def generate_engine_class_header(class_api, used_classes, fully_used_classes, us ) if class_name == "Object": - result.append("") - result.append("\ttemplate ") result.append("\tstatic T *cast_to(Object *p_object);") @@ -1736,7 +1781,6 @@ def generate_engine_class_header(class_api, used_classes, fully_used_classes, us "\tT *get_node(const NodePath &p_path) const { return Object::cast_to(get_node_internal(p_path)); }" ) - result.append("") result.append("};") result.append("") @@ -1820,7 +1864,7 @@ def generate_engine_class_header(class_api, used_classes, fully_used_classes, us result.append(method_body) result.append("\t} \\") - result.append("\t;") + result.append("\t") result.append("") result.append("#define CLASSDB_SINGLETON_VARIANT_CAST \\") @@ -1832,10 +1876,11 @@ def generate_engine_class_header(class_api, used_classes, fully_used_classes, us else: result.append(f'\tVARIANT_ENUM_CAST({class_api["alias_for"]}::{enum_api["name"]}); \\') - result.append("\t;") + result.append("\t") result.append("") result.append(f"#endif // ! {header_guard}") + result.append("") return "\n".join(result) @@ -1857,10 +1902,16 @@ def generate_engine_class_source(class_api, used_classes, fully_used_classes, us result.append("#include ") result.append("") - for included in used_classes: - result.append(f"#include ") - if len(used_classes) > 0: + includes = [] + for included in used_classes: + includes.append(f"godot_cpp/{get_include_path(included)}") + + includes.sort() + + for included in includes: + result.append(f"#include <{included}>") + result.append("") result.append("namespace godot {") @@ -2010,8 +2061,8 @@ def generate_engine_class_source(class_api, used_classes, fully_used_classes, us result.append(method_signature) result.append("") + result.append("} // namespace godot") result.append("") - result.append("} // namespace godot ") return "\n".join(result) @@ -2039,23 +2090,24 @@ def generate_global_constants(api, output_dir): header.append("namespace godot {") header.append("") - for constant in api["global_constants"]: - header.append(f'\tconst int64_t {escape_identifier(constant["name"])} = {constant["value"]};') + if len(api["global_constants"]) > 0: + for constant in api["global_constants"]: + header.append(f'const int64_t {escape_identifier(constant["name"])} = {constant["value"]};') - header.append("") + header.append("") for enum_def in api["global_enums"]: if enum_def["name"].startswith("Variant."): continue if enum_def["is_bitfield"]: - header.append(f'\tenum {enum_def["name"]} : uint64_t {{') + header.append(f'enum {enum_def["name"]} : uint64_t {{') else: - header.append(f'\tenum {enum_def["name"]} {{') + header.append(f'enum {enum_def["name"]} {{') for value in enum_def["values"]: - header.append(f'\t\t{value["name"]} = {value["value"]},') - header.append("\t};") + header.append(f'\t{value["name"]} = {value["value"]},') + header.append("};") header.append("") header.append("} // namespace godot") @@ -2166,13 +2218,25 @@ def generate_utility_functions(api, output_dir): header.append("public:") for function in api["utility_functions"]: + if function["name"] == "is_instance_valid": + # The `is_instance_valid()` function doesn't work as developers expect, and unless used very + # carefully will cause crashes. Instead, developers should use `ObjectDB::get_instance()` + # with object ids to ensure that an instance is still valid. + continue + vararg = "is_vararg" in function and function["is_vararg"] + if vararg: + header.append("") + header.append("private:") + function_signature = "\t" function_signature += make_signature("UtilityFunctions", function, for_header=True, static=True) header.append(function_signature + ";") if vararg: + header.append("") + header.append("public:") # Add templated version. header += make_varargs_template(function, static=True) @@ -2193,13 +2257,16 @@ def generate_utility_functions(api, output_dir): source.append("#include ") source.append("") - source.append("#include ") source.append("#include ") + source.append("#include ") source.append("") source.append("namespace godot {") source.append("") for function in api["utility_functions"]: + if function["name"] == "is_instance_valid": + continue + vararg = "is_vararg" in function and function["is_vararg"] function_signature = make_signature("UtilityFunctions", function) @@ -2293,7 +2360,7 @@ def make_function_parameters(parameters, include_default=False, for_builtin=Fals signature.append(parameter) if is_vararg: - signature.append("const Args&... p_args") + signature.append("const Args &...p_args") return ", ".join(signature) @@ -2351,9 +2418,6 @@ def make_signature( if "is_virtual" in function_data and function_data["is_virtual"]: function_signature += "virtual " - if is_vararg: - function_signature += "private: " - if static: function_signature += "static " @@ -2406,7 +2470,6 @@ def make_varargs_template( function_data, static=False, class_befor_signature="", - with_public_declare=True, with_indent=True, for_builtin_classes=False, ): @@ -2414,10 +2477,7 @@ def make_varargs_template( function_signature = "" - if with_public_declare: - function_signature = "public: " - - function_signature += "template " + result.append("template ") if static: function_signature += "static " @@ -2460,7 +2520,7 @@ def make_varargs_template( function_signature += " {" result.append(function_signature) - args_array = f"\tstd::array variant_args {{ " + args_array = f"\tstd::array variant_args{{ " for argument in method_arguments: if argument["type"] == "Variant": args_array += escape_argument(argument["name"]) diff --git a/cmake/android.cmake b/cmake/android.cmake new file mode 100644 index 000000000..2227f89ce --- /dev/null +++ b/cmake/android.cmake @@ -0,0 +1,41 @@ +#[=======================================================================[.rst: +Android +------- + +This file contains functions for options and configuration for targeting the +Android platform + +Configuration of the Android toolchain is done using toolchain files, +CMakePresets, or variables on the command line. + +The `Android SDK`_ provides toolchain files to help with configuration. + +CMake has its own `built-in support`_ for cross compiling to the +Android platforms. + +.. warning:: + + Android does not support or test the CMake built-in workflow, recommend + using their toolchain file. + +.. _Android SDK:https://developer.android.com/ndk/guides/cmake + +.. _built-in support:https://cmake.org/cmake/help/latest/manual/cmake-toolchains.7.html#cross-compiling-for-android + +There is further information and examples in the doc/cmake.rst file. + +]=======================================================================] +function( android_options ) + # Android Options +endfunction() + +function( android_generate TARGET_NAME ) + + target_compile_definitions(${TARGET_NAME} + PUBLIC + ANDROID_ENABLED + UNIX_ENABLED + ) + + common_compiler_flags( ${TARGET_NAME} ) +endfunction() diff --git a/cmake/common_compiler_flags.cmake b/cmake/common_compiler_flags.cmake index 94556415b..1b185fe2c 100644 --- a/cmake/common_compiler_flags.cmake +++ b/cmake/common_compiler_flags.cmake @@ -1,94 +1,162 @@ -# Add warnings based on compiler & version -# Set some helper variables for readability -set( compiler_less_than_v8 "$,8>" ) -set( compiler_greater_than_or_equal_v9 "$,9>" ) -set( compiler_greater_than_or_equal_v11 "$,11>" ) -set( compiler_less_than_v11 "$,11>" ) -set( compiler_greater_than_or_equal_v12 "$,12>" ) - -# These compiler options reflect what is in godot/SConstruct. -target_compile_options( ${PROJECT_NAME} PRIVATE - # MSVC only - $<${compiler_is_msvc}: - /W4 - - # Disable warnings which we don't plan to fix. - /wd4100 # C4100 (unreferenced formal parameter): Doesn't play nice with polymorphism. - /wd4127 # C4127 (conditional expression is constant) - /wd4201 # C4201 (non-standard nameless struct/union): Only relevant for C89. - /wd4244 # C4244 C4245 C4267 (narrowing conversions): Unavoidable at this scale. - /wd4245 - /wd4267 - /wd4305 # C4305 (truncation): double to float or real_t, too hard to avoid. - /wd4514 # C4514 (unreferenced inline function has been removed) - /wd4714 # C4714 (function marked as __forceinline not inlined) - /wd4820 # C4820 (padding added after construct) - > - - # Clang and GNU common options - $<$: - -Wall - -Wctor-dtor-privacy - -Wextra - -Wno-unused-parameter - -Wnon-virtual-dtor - -Wwrite-strings - > - - # Clang only - $<${compiler_is_clang}: - -Wimplicit-fallthrough - -Wno-ordered-compare-function-pointers - > - - # GNU only - $<${compiler_is_gnu}: - -Walloc-zero - -Wduplicated-branches - -Wduplicated-cond - -Wno-misleading-indentation - -Wplacement-new=1 - -Wshadow-local - -Wstringop-overflow=4 - > - $<$: - # Bogus warning fixed in 8+. - -Wno-strict-overflow - > - $<$: - -Wattribute-alias=2 - > - $<$: - # Broke on MethodBind templates before GCC 11. - -Wlogical-op - > - $<$: - # Regression in GCC 9/10, spams so much in our variadic templates that we need to outright disable it. - -Wno-type-limits - > - $<$: - # False positives in our error macros, see GH-58747. - -Wno-return-type - > -) - -# Treat warnings as errors -function( set_warning_as_error ) - message( STATUS "[${PROJECT_NAME}] Treating warnings as errors") - if ( CMAKE_VERSION VERSION_GREATER_EQUAL "3.24" ) - set_target_properties( ${PROJECT_NAME} - PROPERTIES - COMPILE_WARNING_AS_ERROR ON - ) - else() - target_compile_options( ${PROJECT_NAME} - PRIVATE - $<${compiler_is_msvc}:/WX> - $<$:-Werror> - ) - endif() -endfunction() +#[=======================================================================[.rst: +Common Compiler Flags +--------------------- + +This file contains a single function to configure platform agnostic compiler +flags like optimization levels, warnings, and features. For platform specific +flags look to each of the ``cmake/.cmake`` files. + +]=======================================================================] +#Generator Expression Helpers +set( IS_CLANG "$" ) +set( IS_APPLECLANG "$" ) +set( IS_GNU "$" ) +set( IS_MSVC "$" ) +set( NOT_MSVC "$>" ) + +set( GNU_LT_V8 "$,8>" ) +set( GNU_GE_V9 "$,9>" ) +set( GNU_GT_V11 "$,11>" ) +set( GNU_LT_V11 "$,11>" ) +set( GNU_GE_V12 "$,12>" ) + +set( HOT_RELOAD-UNSET "$") + +set( DISABLE_EXCEPTIONS "$") + + +function( common_compiler_flags TARGET_NAME ) + set( IS_RELEASE "$") + set( DEBUG_FEATURES "$,$>" ) + set( HOT_RELOAD "$,$>" ) + set( DEBUG_SYMBOLS "$" ) + + target_compile_features(${TARGET_NAME} + PUBLIC + cxx_std_17 + ) + + # These compiler options reflect what is in godot/SConstruct. + target_compile_options( ${TARGET_NAME} + PUBLIC + # Disable exception handling. Godot doesn't use exceptions anywhere, and this + # saves around 20% of binary size and very significant build time. + $<${DISABLE_EXCEPTIONS}: + $<${NOT_MSVC}:-fno-exceptions> + > + $<$: + $<${IS_MSVC}:/EHsc> + > + + # Enabling Debug Symbols + $<${DEBUG_SYMBOLS}: + $<${IS_MSVC}: /Zi /FS> + + # Adding dwarf-4 explicitly makes stacktraces work with clang builds, + # otherwise addr2line doesn't understand them. + $<${NOT_MSVC}: + -gdwarf-4 + $ + > + > + + $<${IS_DEV}: + $<${NOT_MSVC}:-fno-omit-frame-pointer -O0 -g> + > + + $<${HOT_RELOAD}: + $<${IS_GNU}:-fno-gnu-unique> + > + + # MSVC only + $<${IS_MSVC}: + "/MP ${PROC_N}" + /W4 + + # Disable warnings which we don't plan to fix. + /wd4100 # C4100 (unreferenced formal parameter): Doesn't play nice with polymorphism. + /wd4127 # C4127 (conditional expression is constant) + /wd4201 # C4201 (non-standard nameless struct/union): Only relevant for C89. + /wd4244 # C4244 C4245 C4267 (narrowing conversions): Unavoidable at this scale. + /wd4245 + /wd4267 + /wd4305 # C4305 (truncation): double to float or real_t, too hard to avoid. + /wd4514 # C4514 (unreferenced inline function has been removed) + /wd4714 # C4714 (function marked as __forceinline not inlined) + /wd4820 # C4820 (padding added after construct) + + /utf-8 + > -if ( GODOT_WARNING_AS_ERROR ) - set_warning_as_error() -endif() + # Clang and GNU common options + $<$: + -Wall + -Wctor-dtor-privacy + -Wextra + -Wno-unused-parameter + -Wnon-virtual-dtor + -Wwrite-strings + > + + # Clang only + $<${IS_CLANG}: + -Wimplicit-fallthrough + -Wno-ordered-compare-function-pointers + > + + # GNU only + $<${IS_GNU}: + -Walloc-zero + -Wduplicated-branches + -Wduplicated-cond + -Wno-misleading-indentation + -Wplacement-new=1 + -Wshadow-local + -Wstringop-overflow=4 + + # Bogus warning fixed in 8+. + $<${GNU_LT_V8}:-Wno-strict-overflow> + + $<${GNU_GE_V9}:-Wattribute-alias=2> + + # Broke on MethodBind templates before GCC 11. + $<${GNU_GT_V11}:-Wlogical-op> + + # Regression in GCC 9/10, spams so much in our variadic templates that we need to outright disable it. + $<${GNU_LT_V11}:-Wno-type-limits> + + # False positives in our error macros, see GH-58747. + $<${GNU_GE_V12}:-Wno-return-type> + > + ) + + target_compile_definitions(${TARGET_NAME} + PUBLIC + GDEXTENSION + + # features + $<${DEBUG_FEATURES}:DEBUG_ENABLED DEBUG_METHODS_ENABLED> + + $<${HOT_RELOAD}:HOT_RELOAD_ENABLED> + + $<$:REAL_T_IS_DOUBLE> + + $<${IS_MSVC}:$<${DISABLE_EXCEPTIONS}:_HAS_EXCEPTIONS=0>> + ) + + target_link_options( ${TARGET_NAME} + PUBLIC + $<${IS_MSVC}: + /WX # treat link warnings as errors. + /MANIFEST:NO # We dont need a manifest + > + + $<${DEBUG_SYMBOLS}:$<${IS_MSVC}:/DEBUG:FULL>> + $<$: + $<${IS_GNU}:-s> + $<${IS_CLANG}:-s> + $<${IS_APPLECLANG}:-Wl,-S -Wl,-x -Wl,-dead_strip> + > + ) + +endfunction() diff --git a/cmake/emsdkHack.cmake b/cmake/emsdkHack.cmake new file mode 100644 index 000000000..6981a3796 --- /dev/null +++ b/cmake/emsdkHack.cmake @@ -0,0 +1,40 @@ +#[=======================================================================[.rst: +emsdkHack +--------- + +The Emscripten platform doesn't support the use of shared libraries as known by cmake. + +* https://github.com/emscripten-core/emscripten/issues/15276 +* https://github.com/emscripten-core/emscripten/issues/17804 + +This workaround only works due to the way the cmake scripts are loaded. + +Prior to the use of ``project( ... )`` directive we need to set +``CMAKE_PROJECT_INCLUDE=cmake/emscripten.cmake``. +This file will be loaded after the toolchain overriding the settings that +prevent shared library building. + +CMAKE_PROJECT_INCLUDE was Added in version 3.15. +``CMAKE_PROJECT__INCLUDE`` was Added in version 3.17: + +More information on cmake's `code injection`_ + +.. _code injection:https://cmake.org/cmake/help/latest/command/project.html#code-injection + +Overwrite Shared Library Properties to allow shared libs to be generated. +]=======================================================================] +if( EMSCRIPTEN ) + set_property(GLOBAL PROPERTY TARGET_SUPPORTS_SHARED_LIBS TRUE) + set(CMAKE_SHARED_LIBRARY_CREATE_C_FLAGS "-sSIDE_MODULE=1") + set(CMAKE_SHARED_LIBRARY_CREATE_CXX_FLAGS "-sSIDE_MODULE=1") + set(CMAKE_SHARED_LIBRARY_SUFFIX) # remove the suffix from the shared lib + set(CMAKE_STRIP FALSE) # used by default in pybind11 on .so modules + + # The Emscripten toolchain sets the default value for EMSCRIPTEN_SYSTEM_PROCESSOR to x86 + # and CMAKE_SYSTEM_PROCESSOR to this value. I don't want that. + set(CMAKE_SYSTEM_PROCESSOR "wasm32" ) + # the above prevents the need for logic like: + #if( ${CMAKE_SYSTEM_NAME} STREQUAL Emscripten ) + # set( SYSTEM_ARCH wasm32 ) + #endif () +endif () diff --git a/cmake/godotcpp.cmake b/cmake/godotcpp.cmake index a5c667796..c6ba53a34 100644 --- a/cmake/godotcpp.cmake +++ b/cmake/godotcpp.cmake @@ -1,13 +1,85 @@ -function( godotcpp_options ) +#[=======================================================================[.rst: +godotcpp.cmake +-------------- + +Because these files are included into the top level CMakelists.txt before the +project directive, it means that + +* ``CMAKE_CURRENT_SOURCE_DIR`` is the location of godot-cpp's CMakeLists.txt +* ``CMAKE_SOURCE_DIR`` is the location where any prior ``project(...)`` + directive was + +]=======================================================================] +include( ${CMAKE_CURRENT_SOURCE_DIR}/cmake/common_compiler_flags.cmake) +include( ${CMAKE_CURRENT_SOURCE_DIR}/cmake/android.cmake) +include( ${CMAKE_CURRENT_SOURCE_DIR}/cmake/ios.cmake) +include( ${CMAKE_CURRENT_SOURCE_DIR}/cmake/linux.cmake) +include( ${CMAKE_CURRENT_SOURCE_DIR}/cmake/macos.cmake) +include( ${CMAKE_CURRENT_SOURCE_DIR}/cmake/web.cmake) +include( ${CMAKE_CURRENT_SOURCE_DIR}/cmake/windows.cmake) + +#Silence warning from unused CMAKE_C_COMPILER from toolchain +if( CMAKE_C_COMPILER ) +endif () + +include(ProcessorCount) +ProcessorCount(PROC_MAX) +message( "Auto-detected ${PROC_MAX} CPU cores available for build parallelism." ) + +# List of known platforms +set( PLATFORM_LIST linux macos windows android ios web ) + +# List of known architectures +set( ARCH_LIST universal x86_32 x86_64 arm32 arm64 rv64 ppc32 ppc64 wasm32 ) + +# Function to map processors to known architectures +function( godot_arch_map ALIAS PROC ) + string( TOLOWER "${PROC}" PROC ) + + if( "${PROC}" IN_LIST ARCH_LIST ) + set( ${ALIAS} "${PROC}" PARENT_SCOPE) + return() + endif() + + set( x86_64 "w64;amd64" ) + set( arm32 "armv7" ) + set( arm64 "armv8;arm64v8;aarch64" ) + set( rv64 "rv;riscv;riscv64" ) + set( ppc32 "ppcle;ppc" ) + set( ppc64 "ppc64le" ) + + if( PROC IN_LIST x86_64 ) + set(${ALIAS} "x86_64" PARENT_SCOPE ) + + elseif( PROC IN_LIST arm32 ) + set(${ALIAS} "arm32" PARENT_SCOPE ) + + elseif( PROC IN_LIST arm64 ) + set(${ALIAS} "arm64" PARENT_SCOPE ) + + elseif( PROC IN_LIST rv64 ) + set(${ALIAS} "rv64" PARENT_SCOPE ) + + elseif( PROC IN_LIST ppc32 ) + set(${ALIAS} "ppc32" PARENT_SCOPE ) - #TODO platform - #TODO target + elseif( PROC IN_LIST ppc64 ) + set(${ALIAS} "ppc64" PARENT_SCOPE ) + + else() + set(${ALIAS} "unknown" PARENT_SCOPE ) + endif () +endfunction() + +# Function to define all the options. +function( godotcpp_options ) + #NOTE: platform is managed using toolchain files. # Input from user for GDExtension interface header and the API JSON file set(GODOT_GDEXTENSION_DIR "gdextension" CACHE PATH "Path to a custom directory containing GDExtension interface header and API JSON file ( /path/to/gdextension_dir )" ) set(GODOT_CUSTOM_API_FILE "" CACHE FILEPATH - "Path to a custom GDExtension API JSON file (takes precedence over `gdextension_dir`) ( /path/to/custom_api_file )") + "Path to a custom GDExtension API JSON file (takes precedence over `GODOT_GDEXTENSION_DIR`) ( /path/to/custom_api_file )") #TODO generate_bindings @@ -19,15 +91,22 @@ function( godotcpp_options ) set(GODOT_PRECISION "single" CACHE STRING "Set the floating-point precision level (single|double)") - #TODO arch + # The arch is typically set by the toolchain + # however for Apple multi-arch setting it here will override. + set( GODOT_ARCH "" CACHE STRING "Target CPU Architecture") + set_property( CACHE GODOT_ARCH PROPERTY STRINGS ${ARCH_LIST} ) + #TODO threads #TODO compiledb #TODO compiledb_file - #TODO build_profile aka cmake preset + + #NOTE: build_profile's equivalent in cmake is CMakePresets.json set(GODOT_USE_HOT_RELOAD "" CACHE BOOL "Enable the extra accounting required to support hot reload. (ON|OFF)") + # Disable exception handling. Godot doesn't use exceptions anywhere, and this + # saves around 20% of binary size and very significant build time (GH-80513). option(GODOT_DISABLE_EXCEPTIONS "Force disabling exception handling code (ON|OFF)" ON ) set( GODOT_SYMBOL_VISIBILITY "hidden" CACHE STRING @@ -36,48 +115,67 @@ function( godotcpp_options ) #TODO optimize #TODO debug_symbols - #TODO dev_build + option( GODOT_DEBUG_SYMBOLS "" OFF ) + option( GODOT_DEV_BUILD "Developer build with dev-only debugging code (DEV_ENABLED)" OFF ) # FIXME These options are not present in SCons, and perhaps should be added there. - option(GODOT_SYSTEM_HEADERS "Expose headers as SYSTEM." ON) - option(GODOT_WARNING_AS_ERROR "Treat warnings as errors" OFF) - - # Run options commands on the following to populate cache for all platforms. - # This type of thing is typically done conditionally - # But as scons shows all options so shall we. - #TODO ios_options() - #TODO linux_options() - #TODO macos_options() - #TODO web_options() - #TODO windows_options() + option( GODOT_SYSTEM_HEADERS "Expose headers as SYSTEM." OFF ) + option( GODOT_WARNING_AS_ERROR "Treat warnings as errors" OFF ) + + # Run options commands on the following to populate cache for all + # platforms. This type of thing is typically done conditionally But as + # scons shows all options so shall we. + android_options() + ios_options() + linux_options() + macos_options() + web_options() + windows_options() endfunction() - +# Function to configure and generate the targets function( godotcpp_generate ) - # Set some helper variables for readability - set( compiler_is_clang "$,$>" ) - set( compiler_is_gnu "$" ) - set( compiler_is_msvc "$" ) - - # CXX_VISIBILITY_PRESET supported values are: default, hidden, protected, and internal - # which is inline with the gcc -fvisibility= - # https://gcc.gnu.org/onlinedocs/gcc/Code-Gen-Options.html - # To match the scons options we need to change the text to match the -fvisibility flag - # it is probably worth another PR which changes both to use the flag options + #[[ Multi-Threaded MSVC Compilation + + When using the MSVC compiler the build command -j only specifies + parallel jobs or targets, and not multi-threaded compilation To speed up + compile times on msvc, the /MP flag can be set. But we need to set it + at configure time. + + MSVC is true when the compiler is some version of Microsoft Visual C++ or + another compiler simulating the Visual C++ cl command-line syntax. ]] + if( MSVC ) + math( EXPR PROC_N "(${PROC_MAX}-1) | (${X}-2)>>31 & 1" ) + message( "Using ${PROC_N} cores for multi-threaded compilation.") + # TODO You can override it at configure time with ...." ) + else () + message( "Using ${CMAKE_BUILD_PARALLEL_LEVEL} cores, You can override" + " it at configure time by using -j or --parallel on the build" + " command.") + message( " eg. cmake --build . -j 7 ...") + endif () + + #[[ GODOT_SYMBOL_VISIBLITY + To match the SCons options, the allowed values are "auto", "visible", and "hidden" + This effects the compiler flag -fvisibility=[default|internal|hidden|protected] + The corresponding CMake option CXX_VISIBILITY_PRESET accepts the compiler values. + + TODO: It is probably worth a pull request which changes both to use the compiler values + https://gcc.gnu.org/onlinedocs/gcc/Code-Gen-Options.html#index-fvisibility + + This performs the necessary conversion + ]] if( ${GODOT_SYMBOL_VISIBILITY} STREQUAL "auto" OR ${GODOT_SYMBOL_VISIBILITY} STREQUAL "visible" ) set( GODOT_SYMBOL_VISIBILITY "default" ) endif () - # Default build type is Debug in the SConstruct - if("${CMAKE_BUILD_TYPE}" STREQUAL "") - set(CMAKE_BUILD_TYPE Debug) - endif() - - # Hot reload is enabled by default in Debug-builds - if( GODOT_USE_HOT_RELOAD STREQUAL "" AND NOT CMAKE_BUILD_TYPE STREQUAL "Release") - set(GODOT_USE_HOT_RELOAD ON) - endif() + # Setup variable to optionally mark headers as SYSTEM + set(GODOT_SYSTEM_HEADERS_ATTRIBUTE "") + if (GODOT_SYSTEM_HEADERS) + set(GODOT_SYSTEM_HEADERS_ATTRIBUTE SYSTEM) + endif () + #[[ Generate Bindings ]] if(NOT DEFINED BITS) set(BITS 32) if(CMAKE_SIZEOF_VOID_P EQUAL 8) @@ -85,67 +183,26 @@ function( godotcpp_generate ) endif(CMAKE_SIZEOF_VOID_P EQUAL 8) endif() - set(GODOT_GDEXTENSION_API_FILE "${GODOT_GDEXTENSION_DIR}/extension_api.json") if (NOT "${GODOT_CUSTOM_API_FILE}" STREQUAL "") # User-defined override. set(GODOT_GDEXTENSION_API_FILE "${GODOT_CUSTOM_API_FILE}") endif() - if ("${GODOT_PRECISION}" STREQUAL "double") - add_definitions(-DREAL_T_IS_DOUBLE) - endif() - - set( GODOT_COMPILE_FLAGS ) - - if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "MSVC") - # using Visual Studio C++ - set(GODOT_COMPILE_FLAGS "/utf-8") # /GF /MP - - if(CMAKE_BUILD_TYPE MATCHES Debug) - set(GODOT_COMPILE_FLAGS "${GODOT_COMPILE_FLAGS} /MDd") # /Od /RTC1 /Zi - else() - set(GODOT_COMPILE_FLAGS "${GODOT_COMPILE_FLAGS} /MD /O2") # /Oy /GL /Gy - endif(CMAKE_BUILD_TYPE MATCHES Debug) - - add_definitions(-DNOMINMAX) - else() # GCC/Clang - if(CMAKE_BUILD_TYPE MATCHES Debug) - set(GODOT_COMPILE_FLAGS "${GODOT_COMPILE_FLAGS} -fno-omit-frame-pointer -O0 -g") - else() - set(GODOT_COMPILE_FLAGS "${GODOT_COMPILE_FLAGS} -O3") - endif(CMAKE_BUILD_TYPE MATCHES Debug) - endif() - - # Disable exception handling. Godot doesn't use exceptions anywhere, and this - # saves around 20% of binary size and very significant build time (GH-80513). - if (GODOT_DISABLE_EXCEPTIONS) - if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "MSVC") - set(GODOT_COMPILE_FLAGS "${GODOT_COMPILE_FLAGS} -D_HAS_EXCEPTIONS=0") - else() - set(GODOT_COMPILE_FLAGS "${GODOT_COMPILE_FLAGS} -fno-exceptions") - endif() - else() - if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "MSVC") - set(GODOT_COMPILE_FLAGS "${GODOT_COMPILE_FLAGS} /EHsc") - endif() - endif() - - # Generate source from the bindings file - find_package(Python3 3.4 REQUIRED) # pathlib should be present + # Code Generation option if(GODOT_GENERATE_TEMPLATE_GET_NODE) set(GENERATE_BINDING_PARAMETERS "True") else() set(GENERATE_BINDING_PARAMETERS "False") endif() - execute_process(COMMAND "${Python3_EXECUTABLE}" "-c" "import binding_generator; binding_generator.print_file_list(\"${GODOT_GDEXTENSION_API_FILE}\", \"${CMAKE_CURRENT_BINARY_DIR}\", headers=True, sources=True)" + execute_process(COMMAND "${Python3_EXECUTABLE}" "-c" "import binding_generator; binding_generator.print_file_list('${GODOT_GDEXTENSION_API_FILE}', '${CMAKE_CURRENT_BINARY_DIR}', headers=True, sources=True)" WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} OUTPUT_VARIABLE GENERATED_FILES_LIST OUTPUT_STRIP_TRAILING_WHITESPACE ) add_custom_command(OUTPUT ${GENERATED_FILES_LIST} - COMMAND "${Python3_EXECUTABLE}" "-c" "import binding_generator; binding_generator.generate_bindings(\"${GODOT_GDEXTENSION_API_FILE}\", \"${GENERATE_BINDING_PARAMETERS}\", \"${BITS}\", \"${GODOT_PRECISION}\", \"${CMAKE_CURRENT_BINARY_DIR}\")" + COMMAND "${Python3_EXECUTABLE}" "-c" "import binding_generator; binding_generator.generate_bindings('${GODOT_GDEXTENSION_API_FILE}', '${GENERATE_BINDING_PARAMETERS}', '${BITS}', '${GODOT_PRECISION}', '${CMAKE_CURRENT_BINARY_DIR}')" VERBATIM WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} MAIN_DEPENDENCY ${GODOT_GDEXTENSION_API_FILE} @@ -153,88 +210,91 @@ function( godotcpp_generate ) COMMENT "Generating bindings" ) - # Get Sources - # As this cmake file was added using 'include(godotcpp)' from the root CMakeLists.txt, - # the ${CMAKE_CURRENT_SOURCE_DIR} is still the root dir. - file(GLOB_RECURSE SOURCES CONFIGURE_DEPENDS src/*.c**) - file(GLOB_RECURSE HEADERS CONFIGURE_DEPENDS include/*.h**) - - # Define our godot-cpp library - add_library(${PROJECT_NAME} STATIC - ${SOURCES} - ${HEADERS} - ${GENERATED_FILES_LIST} - ) - add_library(godot::cpp ALIAS ${PROJECT_NAME}) - - include(${PROJECT_SOURCE_DIR}/cmake/common_compiler_flags.cmake) - - target_compile_features(${PROJECT_NAME} - PRIVATE - cxx_std_17 - ) - - if(GODOT_USE_HOT_RELOAD) - target_compile_definitions(${PROJECT_NAME} PUBLIC HOT_RELOAD_ENABLED) - target_compile_options(${PROJECT_NAME} PUBLIC $<${compiler_is_gnu}:-fno-gnu-unique>) - endif() - - target_compile_definitions(${PROJECT_NAME} PUBLIC - $<$: - DEBUG_ENABLED - DEBUG_METHODS_ENABLED - > - $<${compiler_is_msvc}: - TYPED_METHOD_BIND - > - ) - - target_link_options(${PROJECT_NAME} PRIVATE - $<$: - -static-libgcc - -static-libstdc++ - -Wl,-R,'$$ORIGIN' - > + ### Platform is derived from the toolchain target + # See GeneratorExpressions PLATFORM_ID and CMAKE_SYSTEM_NAME + set( SYSTEM_NAME + $<$:android.${ANDROID_ABI}> + $<$:ios> + $<$:linux> + $<$:macos> + $<$:web> + $<$:windows> + $<$:windows> ) + string(REPLACE ";" "" SYSTEM_NAME "${SYSTEM_NAME}") - # Optionally mark headers as SYSTEM - set(GODOT_SYSTEM_HEADERS_ATTRIBUTE "") - if (GODOT_SYSTEM_HEADERS) - set(GODOT_SYSTEM_HEADERS_ATTRIBUTE SYSTEM) - endif () - - target_include_directories(${PROJECT_NAME} ${GODOT_SYSTEM_HEADERS_ATTRIBUTE} PUBLIC - include - ${CMAKE_CURRENT_BINARY_DIR}/gen/include - ${GODOT_GDEXTENSION_DIR} - ) - - # Add the compile flags - set_property(TARGET ${PROJECT_NAME} APPEND_STRING PROPERTY COMPILE_FLAGS ${GODOT_COMPILE_FLAGS}) - - # Create the correct name (godot.os.build_type.system_bits) - string(TOLOWER "${CMAKE_SYSTEM_NAME}" SYSTEM_NAME) - string(TOLOWER "${CMAKE_BUILD_TYPE}" BUILD_TYPE) - - if(ANDROID) - # Added the android abi after system name - set(SYSTEM_NAME ${SYSTEM_NAME}.${ANDROID_ABI}) - - # Android does not have the bits at the end if you look at the main godot repo build - set(OUTPUT_NAME "godot-cpp.${SYSTEM_NAME}.${BUILD_TYPE}") + ### Use the arch from the toolchain if it isn't set manually + if( GODOT_ARCH ) + set(SYSTEM_ARCH ${GODOT_ARCH}) else() - set(OUTPUT_NAME "godot-cpp.${SYSTEM_NAME}.${BUILD_TYPE}.${BITS}") + godot_arch_map( SYSTEM_ARCH ${CMAKE_SYSTEM_PROCESSOR} ) endif() - set_target_properties(${PROJECT_NAME} - PROPERTIES - CXX_EXTENSIONS OFF - POSITION_INDEPENDENT_CODE ON - CXX_VISIBILITY_PRESET ${GODOT_SYMBOL_VISIBILITY} - ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/bin" - LIBRARY_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/bin" - RUNTIME_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/bin" - OUTPUT_NAME "${OUTPUT_NAME}" - ) + ### Define our godot-cpp library targets + foreach ( TARGET_NAME template_debug template_release editor ) + + # Useful genex snippits used in subsequent genex's + set( IS_RELEASE "$") + set( IS_DEV "$") + set( DEBUG_FEATURES "$,$>" ) + set( HOT_RELOAD "$,$>" ) + + # the godot-cpp.* library targets + add_library( ${TARGET_NAME} STATIC ${EXCLUDE} ) + add_library( godot-cpp::${TARGET_NAME} ALIAS ${TARGET_NAME} ) + + file( GLOB_RECURSE GODOTCPP_SOURCES LIST_DIRECTORIES NO CONFIGURE_DEPENDS src/*.cpp ) + + target_sources( ${TARGET_NAME} + PRIVATE + ${GODOTCPP_SOURCES} + ${GENERATED_FILES_LIST} + ) + + target_include_directories( ${TARGET_NAME} ${GODOT_SYSTEM_HEADERS_ATTRIBUTE} PUBLIC + include + ${CMAKE_CURRENT_BINARY_DIR}/gen/include + ${GODOT_GDEXTENSION_DIR} + ) + + set_target_properties( ${TARGET_NAME} + PROPERTIES + CXX_STANDARD 17 + CXX_EXTENSIONS OFF + CXX_VISIBILITY_PRESET ${GODOT_SYMBOL_VISIBILITY} + + COMPILE_WARNING_AS_ERROR ${GODOT_WARNING_AS_ERROR} + POSITION_INDEPENDENT_CODE ON + BUILD_RPATH_USE_ORIGIN ON + + PREFIX lib + OUTPUT_NAME "${PROJECT_NAME}.${SYSTEM_NAME}.${TARGET_NAME}.${SYSTEM_ARCH}" + ARCHIVE_OUTPUT_DIRECTORY "$<1:${CMAKE_BINARY_DIR}/bin>" + + # Things that are handy to know for dependent targets + GODOT_PLATFORM "${SYSTEM_NAME}" + GODOT_TARGET "${TARGET_NAME}" + GODOT_ARCH "${SYSTEM_ARCH}" + ) + + if( CMAKE_SYSTEM_NAME STREQUAL Android ) + android_generate( ${TARGET_NAME} ) + elseif ( CMAKE_SYSTEM_NAME STREQUAL iOS ) + ios_generate( ${TARGET_NAME} ) + elseif ( CMAKE_SYSTEM_NAME STREQUAL Linux ) + linux_generate( ${TARGET_NAME} ) + elseif ( CMAKE_SYSTEM_NAME STREQUAL Darwin ) + macos_generate( ${TARGET_NAME} ) + elseif ( CMAKE_SYSTEM_NAME STREQUAL Emscripten ) + web_generate( ${TARGET_NAME} ) + elseif ( CMAKE_SYSTEM_NAME STREQUAL Windows ) + windows_generate( ${TARGET_NAME} ) + endif () + + endforeach () + + # Added for backwards compatibility with prior cmake solution so that builds dont immediately break + # from a missing target. + add_library( godot::cpp ALIAS template_debug ) endfunction() diff --git a/cmake/ios.cmake b/cmake/ios.cmake new file mode 100644 index 000000000..bb9642210 --- /dev/null +++ b/cmake/ios.cmake @@ -0,0 +1,22 @@ +#[=======================================================================[.rst: +Ios +--- + +This file contains functions for options and configuration for targeting the +Ios platform + +]=======================================================================] +function(ios_options) + # iOS options +endfunction() + +function(ios_generate TARGET_NAME) + + target_compile_definitions(${TARGET_NAME} + PUBLIC + IOS_ENABLED + UNIX_ENABLED + ) + + common_compiler_flags(${TARGET_NAME}) +endfunction() diff --git a/cmake/linux.cmake b/cmake/linux.cmake new file mode 100644 index 000000000..fae620e0e --- /dev/null +++ b/cmake/linux.cmake @@ -0,0 +1,22 @@ +#[=======================================================================[.rst: +Linux +----- + +This file contains functions for options and configuration for targeting the +Linux platform + +]=======================================================================] +function( linux_options ) + # Linux Options +endfunction() + +function( linux_generate TARGET_NAME ) + + target_compile_definitions( ${TARGET_NAME} + PUBLIC + LINUX_ENABLED + UNIX_ENABLED + ) + + common_compiler_flags( ${TARGET_NAME} ) +endfunction() diff --git a/cmake/macos.cmake b/cmake/macos.cmake new file mode 100644 index 000000000..1bb4dbcfb --- /dev/null +++ b/cmake/macos.cmake @@ -0,0 +1,59 @@ +#[=======================================================================[.rst: +MacOS +----- + +This file contains functions for options and configuration for targeting the +MacOS platform + +]=======================================================================] + +# Find Requirements +IF(APPLE) + set( CMAKE_OSX_SYSROOT $ENV{SDKROOT} ) + find_library( COCOA_LIBRARY REQUIRED + NAMES Cocoa + PATHS ${CMAKE_OSX_SYSROOT}/System/Library + PATH_SUFFIXES Frameworks + NO_DEFAULT_PATH) +ENDIF (APPLE) + + +function( macos_options ) + # macos options here +endfunction() + + +function( macos_generate TARGET_NAME ) + + # OSX_ARCHITECTURES does not support generator expressions. + if( NOT GODOT_ARCH OR GODOT_ARCH STREQUAL universal ) + set( OSX_ARCH "x86_64;arm64" ) + set( SYSTEM_ARCH universal ) + else() + set( OSX_ARCH ${GODOT_ARCH} ) + endif() + + set_target_properties( ${TARGET_NAME} + PROPERTIES + + OSX_ARCHITECTURES "${OSX_ARCH}" + ) + + target_compile_definitions(${TARGET_NAME} + PUBLIC + MACOS_ENABLED + UNIX_ENABLED + ) + + target_link_options( ${TARGET_NAME} + PUBLIC + -Wl,-undefined,dynamic_lookup + ) + + target_link_libraries( ${TARGET_NAME} + INTERFACE + ${COCOA_LIBRARY} + ) + + common_compiler_flags( ${TARGET_NAME} ) +endfunction() diff --git a/cmake/web.cmake b/cmake/web.cmake new file mode 100644 index 000000000..b7d6c13aa --- /dev/null +++ b/cmake/web.cmake @@ -0,0 +1,42 @@ +#[=======================================================================[.rst: +Web +--- + +This file contains functions for options and configuration for targeting the +Web platform + +]=======================================================================] + +# Emscripten requires this hack for use of the SHARED option +set( CMAKE_PROJECT_godot-cpp_INCLUDE cmake/emsdkHack.cmake ) + +function( web_options ) + # web options +endfunction() + + +function( web_generate TARGET_NAME ) + + target_compile_definitions(${TARGET_NAME} + PUBLIC + WEB_ENABLED + UNIX_ENABLED + ) + + target_compile_options( ${TARGET_NAME} + PUBLIC + -sSIDE_MODULE + -sSUPPORT_LONGJMP=wasm + -fno-exceptions + ) + + target_link_options( ${TARGET_NAME} + INTERFACE + -sWASM_BIGINT + -sSUPPORT_LONGJMP=wasm + -fvisibility=hidden + -shared + ) + + common_compiler_flags( ${TARGET_NAME} ) +endfunction() diff --git a/cmake/windows.cmake b/cmake/windows.cmake new file mode 100644 index 000000000..bf17f74f9 --- /dev/null +++ b/cmake/windows.cmake @@ -0,0 +1,58 @@ +#[=======================================================================[.rst: +Windows +------- + +This file contains functions for options and configuration for targeting the +Windows platform + +]=======================================================================] + +function( windows_options ) + + option( GODOT_USE_STATIC_CPP "Link MinGW/MSVC C++ runtime libraries statically" ON ) + + option( GODOT_DEBUG_CRT "Compile with MSVC's debug CRT (/MDd)" OFF ) + +endfunction() + +function( windows_generate TARGET_NAME ) + set( IS_MSVC "$" ) + set( IS_CLANG "$,$>" ) + set( NOT_MSVC "$" ) + set( STATIC_CPP "$") + set( DISABLE_EXCEPTIONS "$") + set( DEBUG_CRT "$" ) + + set_target_properties( ${TARGET_NAME} + PROPERTIES + PDB_OUTPUT_DIRECTORY "$<1:${CMAKE_SOURCE_DIR}/bin>" + INTERFACE_MSVC_RUNTIME_LIBRARY + "$>" + ) + + target_compile_definitions( ${TARGET_NAME} + PUBLIC + WINDOWS_ENABLED + $<${IS_MSVC}: + TYPED_METHOD_BIND + NOMINMAX + > + ) + + target_link_options( ${TARGET_NAME} + PUBLIC + + $<${NOT_MSVC}: + -Wl,--no-undefined + $<${STATIC_CPP}: + -static + -static-libgcc + -static-libstdc++ + > + > + + $<${IS_CLANG}:-lstdc++> + ) + + common_compiler_flags( ${TARGET_NAME} ) +endfunction() diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 42ea6e086..ab7397fa2 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -1,143 +1,67 @@ -cmake_minimum_required(VERSION 3.13) -project(godot-cpp-test) - -set(GODOT_GDEXTENSION_DIR ../gdextension/ CACHE STRING "Path to GDExtension interface header directory") -set(CPP_BINDINGS_PATH ../ CACHE STRING "Path to C++ bindings") - -if(CMAKE_SYSTEM_NAME STREQUAL "Linux") - set(TARGET_PATH x11) -elseif(CMAKE_SYSTEM_NAME STREQUAL "Windows") - set(TARGET_PATH win64) -elseif(CMAKE_SYSTEM_NAME STREQUAL "Darwin") - set(TARGET_PATH macos) -else() - message(FATAL_ERROR "Not implemented support for ${CMAKE_SYSTEM_NAME}") -endif() - -# Change the output directory to the bin directory -set(BUILD_PATH ${CMAKE_SOURCE_DIR}/bin/${TARGET_PATH}) -set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY "${BUILD_PATH}") -set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${BUILD_PATH}") -set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${BUILD_PATH}") -SET(CMAKE_RUNTIME_OUTPUT_DIRECTORY_DEBUG "${BUILD_PATH}") -SET(CMAKE_RUNTIME_OUTPUT_DIRECTORY_RELEASE "${BUILD_PATH}") -SET(CMAKE_LIBRARY_OUTPUT_DIRECTORY_DEBUG "${BUILD_PATH}") -SET(CMAKE_LIBRARY_OUTPUT_DIRECTORY_RELEASE "${BUILD_PATH}") -SET(CMAKE_ARCHIVE_OUTPUT_DIRECTORY_DEBUG "${BUILD_PATH}") -SET(CMAKE_ARCHIVE_OUTPUT_DIRECTORY_RELEASE "${BUILD_PATH}") - -# Set the c++ standard to c++17 -set(CMAKE_CXX_STANDARD 17) -set(CMAKE_CXX_STANDARD_REQUIRED ON) -set(CMAKE_CXX_EXTENSIONS OFF) - -set(GODOT_COMPILE_FLAGS ) -set(GODOT_LINKER_FLAGS ) - -if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "MSVC") - # using Visual Studio C++ - set(GODOT_COMPILE_FLAGS "${GODOT_COMPILE_FLAGS} /WX") # /GF /MP - set(GODOT_COMPILE_FLAGS "${GODOT_COMPILE_FLAGS} /DTYPED_METHOD_BIND") - - if(CMAKE_BUILD_TYPE MATCHES Debug) - set(GODOT_COMPILE_FLAGS "${GODOT_COMPILE_FLAGS} /MDd") # /Od /RTC1 /Zi - else() - set(GODOT_COMPILE_FLAGS "${GODOT_COMPILE_FLAGS} /MD /O2") # /Oy /GL /Gy - STRING(REGEX REPLACE "/RTC(su|[1su])" "" CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}") - string(REPLACE "/RTC1" "" CMAKE_CXX_FLAGS_DEBUG ${CMAKE_CXX_FLAGS_DEBUG}) - endif(CMAKE_BUILD_TYPE MATCHES Debug) - - # Disable conversion warning, truncation, unreferenced var, signed mismatch - set(GODOT_COMPILE_FLAGS "${GODOT_COMPILE_FLAGS} /wd4244 /wd4305 /wd4101 /wd4018 /wd4267") - - add_definitions(-DNOMINMAX) - - # Unkomment for warning level 4 - #if(CMAKE_CXX_FLAGS MATCHES "/W[0-4]") - # string(REGEX REPLACE "/W[0-4]" "" CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}") - #endif() +# Testing Extension +# This is only linked to the template_release config. +# so it requires the template_release version of godot to run. + +add_library( godot-cpp-test SHARED EXCLUDE_FROM_ALL ) + +target_sources( godot-cpp-test + PRIVATE + src/example.cpp + src/example.h + src/register_types.cpp + src/register_types.h + src/tests.h +) -else() +set( TEST_TARGET "template_debug" CACHE STRING "Which godot-cpp::target to link against" ) +set_property( CACHE TEST_TARGET PROPERTY STRINGS "template_debug;template_release;editor" ) - set(GODOT_LINKER_FLAGS "-static-libgcc -static-libstdc++ -Wl,-R,'$$ORIGIN'") +target_link_libraries( godot-cpp-test + PRIVATE + godot-cpp::${TEST_TARGET} ) - set(GODOT_COMPILE_FLAGS "-fPIC -g -Wwrite-strings") +### Get useful properties of the library +get_target_property( GODOT_PLATFORM godot-cpp::${TEST_TARGET} GODOT_PLATFORM ) +get_target_property( GODOT_TARGET godot-cpp::${TEST_TARGET} GODOT_TARGET ) +get_target_property( GODOT_ARCH godot-cpp::${TEST_TARGET} GODOT_ARCH ) - if(CMAKE_BUILD_TYPE MATCHES Debug) - set(GODOT_COMPILE_FLAGS "${GODOT_COMPILE_FLAGS} -fno-omit-frame-pointer -O0") - else() - set(GODOT_COMPILE_FLAGS "${GODOT_COMPILE_FLAGS} -O3") - endif(CMAKE_BUILD_TYPE MATCHES Debug) -endif() +set( OUTPUT_DIR "${CMAKE_CURRENT_SOURCE_DIR}/project/bin/" ) -# Disable exception handling. Godot doesn't use exceptions anywhere, and this -# saves around 20% of binary size and very significant build time (GH-80513). -option(GODOT_DISABLE_EXCEPTIONS ON "Force disabling exception handling code") -if (GODOT_DISABLE_EXCEPTIONS) - if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "MSVC") - set(GODOT_COMPILE_FLAGS "${GODOT_COMPILE_FLAGS} -D_HAS_EXCEPTIONS=0") - else() - set(GODOT_COMPILE_FLAGS "${GODOT_COMPILE_FLAGS} -fno-exceptions") - endif() -else() - if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "MSVC") - set(GODOT_COMPILE_FLAGS "${GODOT_COMPILE_FLAGS} /EHsc") - endif() -endif() +set_target_properties( godot-cpp-test + PROPERTIES + CXX_STANDARD 17 + CXX_EXTENSIONS OFF + CXX_VISIBILITY_PRESET ${GODOT_SYMBOL_VISIBILITY} -# Get Sources -file(GLOB_RECURSE SOURCES src/*.c**) -file(GLOB_RECURSE HEADERS include/*.h**) + POSITION_INDEPENDENT_CODE ON + BUILD_RPATH_USE_ORIGIN ON + LINK_SEARCH_START_STATIC ON + LINK_SEARCH_END_STATIC ON -# Define our godot-cpp library -add_library(${PROJECT_NAME} SHARED ${SOURCES} ${HEADERS}) + # NOTE: Wrapping the output variables inside a generator expression + # prevents msvc generator from adding addition Config Directories + LIBRARY_OUTPUT_DIRECTORY "$<1:${OUTPUT_DIR}>" + RUNTIME_OUTPUT_DIRECTORY "$<1:${OUTPUT_DIR}>" + PDB_OUTPUT_DIRECTORY "$<1:${OUTPUT_DIR}>" #MSVC Only, ignored on other platforms -target_include_directories(${PROJECT_NAME} SYSTEM - PRIVATE - ${CPP_BINDINGS_PATH}/include - ${CPP_BINDINGS_PATH}/gen/include - ${GODOT_GDEXTENSION_DIR} + PREFIX "lib" + OUTPUT_NAME "gdexample.${GODOT_PLATFORM}.${GODOT_TARGET}.${GODOT_ARCH}" ) -# Create the correct name (godot.os.build_type.system_bits) -# Synchronized with godot-cpp's CMakeLists.txt - -set(BITS 32) -if(CMAKE_SIZEOF_VOID_P EQUAL 8) - set(BITS 64) -endif(CMAKE_SIZEOF_VOID_P EQUAL 8) - -if(CMAKE_BUILD_TYPE MATCHES Debug) - set(GODOT_CPP_BUILD_TYPE Debug) -else() - set(GODOT_CPP_BUILD_TYPE Release) -endif() - -string(TOLOWER ${CMAKE_SYSTEM_NAME} SYSTEM_NAME) -string(TOLOWER ${GODOT_CPP_BUILD_TYPE} BUILD_TYPE) - -if(ANDROID) - # Added the android abi after system name - set(SYSTEM_NAME ${SYSTEM_NAME}.${ANDROID_ABI}) -endif() +if( CMAKE_SYSTEM_NAME STREQUAL Darwin ) + get_target_property( OSX_ARCH godot-cpp::${TEST_TARGET} OSX_ARCHITECTURES ) -if(CMAKE_VERSION VERSION_GREATER "3.13") - target_link_directories(${PROJECT_NAME} - PRIVATE - ${CPP_BINDINGS_PATH}/bin/ - ) + set( OUTPUT_DIR "${OUTPUT_DIR}/libgdexample.macos.${TEST_TARGET}.framework") - target_link_libraries(${PROJECT_NAME} - godot-cpp.${SYSTEM_NAME}.${BUILD_TYPE}$<$>:.${BITS}> - ) -else() - target_link_libraries(${PROJECT_NAME} - ${CPP_BINDINGS_PATH}/bin/libgodot-cpp.${SYSTEM_NAME}.${BUILD_TYPE}$<$>:.${BITS}>.a - ) -endif() + set_target_properties( godot-cpp-test + PROPERTIES + LIBRARY_OUTPUT_DIRECTORY "$<1:${OUTPUT_DIR}>" + RUNTIME_OUTPUT_DIRECTORY "$<1:${OUTPUT_DIR}>" -# Add the compile flags -set_property(TARGET ${PROJECT_NAME} APPEND_STRING PROPERTY COMPILE_FLAGS ${GODOT_COMPILE_FLAGS}) -set_property(TARGET ${PROJECT_NAME} APPEND_STRING PROPERTY LINK_FLAGS ${GODOT_LINKER_FLAGS}) + OUTPUT_NAME "gdexample.macos.${TEST_TARGET}" + SUFFIX "" -set_property(TARGET ${PROJECT_NAME} PROPERTY OUTPUT_NAME "gdexample") + #macos options + OSX_ARCHITECTURES "${OSX_ARCH}" + ) +endif () diff --git a/tools/android.py b/tools/android.py index 0222121eb..fee4ed255 100644 --- a/tools/android.py +++ b/tools/android.py @@ -120,4 +120,9 @@ def generate(env): env.Append(CPPDEFINES=["ANDROID_ENABLED", "UNIX_ENABLED"]) + # Refer to https://github.com/godotengine/godot/blob/master/platform/android/detect.py + # LTO benefits for Android (size, performance) haven't been clearly established yet. + if env["lto"] == "auto": + env["lto"] = "none" + common_compiler_flags.generate(env) diff --git a/tools/common_compiler_flags.py b/tools/common_compiler_flags.py index 6a1fb6931..e645f390f 100644 --- a/tools/common_compiler_flags.py +++ b/tools/common_compiler_flags.py @@ -22,6 +22,10 @@ def exists(env): def generate(env): + assert env["lto"] in ["thin", "full", "none"], "Unrecognized lto: {}".format(env["lto"]) + if env["lto"] != "none": + print("Using LTO: " + env["lto"]) + # Require C++17 if env.get("is_msvc", False): env.Append(CXXFLAGS=["/std:c++17"]) @@ -64,6 +68,22 @@ def generate(env): env.Append(LINKFLAGS=["/OPT:REF"]) elif env["optimize"] == "debug" or env["optimize"] == "none": env.Append(CCFLAGS=["/Od"]) + + if env["lto"] == "thin": + if not env["use_llvm"]: + print("ThinLTO is only compatible with LLVM, use `use_llvm=yes` or `lto=full`.") + env.Exit(255) + + env.Append(CCFLAGS=["-flto=thin"]) + env.Append(LINKFLAGS=["-flto=thin"]) + elif env["lto"] == "full": + if env["use_llvm"]: + env.Append(CCFLAGS=["-flto"]) + env.Append(LINKFLAGS=["-flto"]) + else: + env.AppendUnique(CCFLAGS=["/GL"]) + env.AppendUnique(ARFLAGS=["/LTCG"]) + env.AppendUnique(LINKFLAGS=["/LTCG"]) else: if env["debug_symbols"]: # Adding dwarf-4 explicitly makes stacktraces work with clang builds, @@ -91,3 +111,13 @@ def generate(env): env.Append(CCFLAGS=["-Og"]) elif env["optimize"] == "none": env.Append(CCFLAGS=["-O0"]) + + if env["lto"] == "thin": + if (env["platform"] == "windows" or env["platform"] == "linux") and not env["use_llvm"]: + print("ThinLTO is only compatible with LLVM, use `use_llvm=yes` or `lto=full`.") + env.Exit(255) + env.Append(CCFLAGS=["-flto=thin"]) + env.Append(LINKFLAGS=["-flto=thin"]) + elif env["lto"] == "full": + env.Append(CCFLAGS=["-flto"]) + env.Append(LINKFLAGS=["-flto"]) diff --git a/tools/godotcpp.py b/tools/godotcpp.py index b2a63dc1e..77a0740fc 100644 --- a/tools/godotcpp.py +++ b/tools/godotcpp.py @@ -326,6 +326,14 @@ def options(opts, env): ("none", "custom", "debug", "speed", "speed_trace", "size"), ) ) + opts.Add( + EnumVariable( + "lto", + "Link-time optimization", + "none", + ("none", "auto", "thin", "full"), + ) + ) opts.Add(BoolVariable("debug_symbols", "Build with debugging symbols", True)) opts.Add(BoolVariable("dev_build", "Developer build with dev-only debugging code (DEV_ENABLED)", False)) opts.Add(BoolVariable("verbose", "Enable verbose output for the compilation", False)) diff --git a/tools/ios.py b/tools/ios.py index 9675ab1ac..25c17db31 100644 --- a/tools/ios.py +++ b/tools/ios.py @@ -97,4 +97,9 @@ def generate(env): env.Append(CPPDEFINES=["IOS_ENABLED", "UNIX_ENABLED"]) + # Refer to https://github.com/godotengine/godot/blob/master/platform/ios/detect.py: + # Disable by default as it makes linking in Xcode very slow. + if env["lto"] == "auto": + env["lto"] = "none" + common_compiler_flags.generate(env) diff --git a/tools/linux.py b/tools/linux.py index 0b2687880..9e85d8803 100644 --- a/tools/linux.py +++ b/tools/linux.py @@ -39,4 +39,8 @@ def generate(env): env.Append(CPPDEFINES=["LINUX_ENABLED", "UNIX_ENABLED"]) + # Refer to https://github.com/godotengine/godot/blob/master/platform/linuxbsd/detect.py + if env["lto"] == "auto": + env["lto"] = "full" + common_compiler_flags.generate(env) diff --git a/tools/macos.py b/tools/macos.py index 741815051..f88e47ff1 100644 --- a/tools/macos.py +++ b/tools/macos.py @@ -73,4 +73,9 @@ def generate(env): env.Append(CPPDEFINES=["MACOS_ENABLED", "UNIX_ENABLED"]) + # Refer to https://github.com/godotengine/godot/blob/master/platform/macos/detect.py + # LTO benefits for macOS (size, performance) haven't been clearly established yet. + if env["lto"] == "auto": + env["lto"] = "none" + common_compiler_flags.generate(env) diff --git a/tools/web.py b/tools/web.py index e878a78f3..f1a441869 100644 --- a/tools/web.py +++ b/tools/web.py @@ -52,4 +52,8 @@ def generate(env): env.Append(CPPDEFINES=["WEB_ENABLED", "UNIX_ENABLED"]) + # Refer to https://github.com/godotengine/godot/blob/master/platform/web/detect.py + if env["lto"] == "auto": + env["lto"] = "full" + common_compiler_flags.generate(env) diff --git a/tools/windows.py b/tools/windows.py index 2e8d609ea..f66bce423 100644 --- a/tools/windows.py +++ b/tools/windows.py @@ -78,6 +78,7 @@ def options(opts): opts.Add(BoolVariable("use_mingw", "Use the MinGW compiler instead of MSVC - only effective on Windows", False)) opts.Add(BoolVariable("use_static_cpp", "Link MinGW/MSVC C++ runtime libraries statically", True)) opts.Add(BoolVariable("silence_msvc", "Silence MSVC's cl/link stdout bloat, redirecting errors to stderr.", True)) + opts.Add(BoolVariable("debug_crt", "Compile with MSVC's debug CRT (/MDd)", False)) opts.Add(BoolVariable("use_llvm", "Use the LLVM compiler (MVSC or MinGW depending on the use_mingw flag)", False)) opts.Add("mingw_prefix", "MinGW prefix", mingw) @@ -117,10 +118,14 @@ def generate(env): env["CC"] = "clang-cl" env["CXX"] = "clang-cl" - if env["use_static_cpp"]: - env.Append(CCFLAGS=["/MT"]) + if env["debug_crt"]: + # Always use dynamic runtime, static debug CRT breaks thread_local. + env.AppendUnique(CCFLAGS=["/MDd"]) else: - env.Append(CCFLAGS=["/MD"]) + if env["use_static_cpp"]: + env.AppendUnique(CCFLAGS=["/MT"]) + else: + env.AppendUnique(CCFLAGS=["/MD"]) if env["silence_msvc"] and not env.GetOption("clean"): silence_msvc(env) @@ -198,4 +203,12 @@ def generate(env): env.Append(CPPDEFINES=["WINDOWS_ENABLED"]) + # Refer to https://github.com/godotengine/godot/blob/master/platform/windows/detect.py + if env["lto"] == "auto": + if env.get("is_msvc", False): + # No LTO by default for MSVC, doesn't help. + env["lto"] = "none" + else: # Release + env["lto"] = "full" + common_compiler_flags.generate(env)