From e6fa97f32dcd876ae272c6c99e3282bcfd32eca0 Mon Sep 17 00:00:00 2001 From: Fabio Alessandrelli Date: Wed, 21 May 2025 21:10:42 +0200 Subject: [PATCH] windows: Add arm64 build --- .github/workflows/build_release.yml | 16 +- SConstruct | 5 - misc/patches/common_compiler_flags.py | 123 ++++++++++++++ misc/patches/windows.py | 221 ++++++++++++++++++++++++++ misc/scripts/package_release.sh | 2 + misc/webrtc.gdextension | 2 + 6 files changed, 363 insertions(+), 6 deletions(-) create mode 100644 misc/patches/common_compiler_flags.py create mode 100644 misc/patches/windows.py diff --git a/.github/workflows/build_release.yml b/.github/workflows/build_release.yml index 5ae948a..48ae941 100644 --- a/.github/workflows/build_release.yml +++ b/.github/workflows/build_release.yml @@ -128,6 +128,12 @@ jobs: os: 'ubuntu-22.04' msvc_arch: amd64 cache-name: win-x86_64 + - platform: windows + arch: 'arm64' + gdnative_flags: '' + sconsflags: '' + os: 'windows-latest' + cache-name: win-arm64 env: SCONS_CACHE: ${{ github.workspace }}/.scons-cache/ @@ -149,7 +155,7 @@ jobs: continue-on-error: true - name: Install Windows build dependencies - if: ${{ matrix.platform == 'windows' }} + if: ${{ matrix.platform == 'windows' && matrix.os != 'windows-latest' }} run: | sudo apt-get update sudo apt-get install build-essential mingw-w64 @@ -211,6 +217,13 @@ jobs: patch -p1 < misc/patches/build_profile.diff patch -p1 < misc/patches/build_profile_3.x.diff + - name: Use updated windows tools. + if: ${{ matrix.os == 'windows-latest' }} + run: | + rm /usr/bin/link # GNU linkker conflicts with MSVC linkes + cp misc/patches/windows.py godot-cpp/tools/windows.py + cp misc/patches/common_compiler_flags.py godot-cpp/tools/common_compiler_flags.py + - name: Print tools versions run: | python --version @@ -226,6 +239,7 @@ jobs: scons target=template_release godot_version=4 - name: Compile GDNative (3.5+) - release ${{ matrix.platform }} - ${{ matrix.arch }} + if: ${{ ! (matrix.platform == 'windows' && matrix.os == 'windows-latest' && matrix.arch == 'arm64') }} run: | scons target=release generate_bindings=yes ${{ matrix.gdnative_flags }} godot_version=3 diff --git a/SConstruct b/SConstruct index e349b73..ee93345 100644 --- a/SConstruct +++ b/SConstruct @@ -127,11 +127,6 @@ else: cpp_env = SConscript(sconstruct) env = cpp_env.Clone() -if cpp_env.get("is_msvc", False): - # Make sure we don't build with static cpp on MSVC (default in recent godot-cpp versions). - replace_flags(env["CCFLAGS"], {"/MT": "/MD"}) - replace_flags(cpp_env["CCFLAGS"], {"/MT": "/MD"}) - if env["platform"] == "ios": if env["ios_simulator"]: env.AppendUnique(LINKFLAGS=["-mios-simulator-version-min=" + ARGUMENTS["ios_min_version"]]) diff --git a/misc/patches/common_compiler_flags.py b/misc/patches/common_compiler_flags.py new file mode 100644 index 0000000..e645f39 --- /dev/null +++ b/misc/patches/common_compiler_flags.py @@ -0,0 +1,123 @@ +import os +import subprocess + + +def using_clang(env): + return "clang" in os.path.basename(env["CC"]) + + +def is_vanilla_clang(env): + if not using_clang(env): + return False + try: + version = subprocess.check_output([env.subst(env["CXX"]), "--version"]).strip().decode("utf-8") + except (subprocess.CalledProcessError, OSError): + print("Couldn't parse CXX environment variable to infer compiler version.") + return False + return not version.startswith("Apple") + + +def exists(env): + return True + + +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"]) + else: + env.Append(CXXFLAGS=["-std=c++17"]) + + # Disable exception handling. Godot doesn't use exceptions anywhere, and this + # saves around 20% of binary size and very significant build time. + if env["disable_exceptions"]: + if env.get("is_msvc", False): + env.Append(CPPDEFINES=[("_HAS_EXCEPTIONS", 0)]) + else: + env.Append(CXXFLAGS=["-fno-exceptions"]) + elif env.get("is_msvc", False): + env.Append(CXXFLAGS=["/EHsc"]) + + if not env.get("is_msvc", False): + if env["symbols_visibility"] == "visible": + env.Append(CCFLAGS=["-fvisibility=default"]) + env.Append(LINKFLAGS=["-fvisibility=default"]) + elif env["symbols_visibility"] == "hidden": + env.Append(CCFLAGS=["-fvisibility=hidden"]) + env.Append(LINKFLAGS=["-fvisibility=hidden"]) + + # Set optimize and debug_symbols flags. + # "custom" means do nothing and let users set their own optimization flags. + if env.get("is_msvc", False): + if env["debug_symbols"]: + env.Append(CCFLAGS=["/Zi", "/FS"]) + env.Append(LINKFLAGS=["/DEBUG:FULL"]) + + 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"]) + 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, + # otherwise addr2line doesn't understand them. + env.Append(CCFLAGS=["-gdwarf-4"]) + if env.dev_build: + env.Append(CCFLAGS=["-g3"]) + else: + env.Append(CCFLAGS=["-g2"]) + else: + if using_clang(env) and not is_vanilla_clang(env) and not env["use_mingw"]: + # Apple Clang, its linker doesn't like -s. + env.Append(LINKFLAGS=["-Wl,-S", "-Wl,-x", "-Wl,-dead_strip"]) + else: + env.Append(LINKFLAGS=["-s"]) + + if env["optimize"] == "speed": + env.Append(CCFLAGS=["-O3"]) + # `-O2` is friendlier to debuggers than `-O3`, leading to better crash backtraces. + elif env["optimize"] == "speed_trace": + env.Append(CCFLAGS=["-O2"]) + elif env["optimize"] == "size": + env.Append(CCFLAGS=["-Os"]) + elif env["optimize"] == "debug": + 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/misc/patches/windows.py b/misc/patches/windows.py new file mode 100644 index 0000000..a5f2105 --- /dev/null +++ b/misc/patches/windows.py @@ -0,0 +1,221 @@ +import os +import sys + +import common_compiler_flags +import my_spawn +from SCons.Tool import mingw, msvc +from SCons.Variables import BoolVariable + + +def silence_msvc(env): + import os + import re + import tempfile + + # Ensure we have a location to write captured output to, in case of false positives. + capture_path = os.path.join(os.path.dirname(__file__), "..", "msvc_capture.log") + with open(capture_path, "wt", encoding="utf-8"): + pass + + old_spawn = env["SPAWN"] + re_redirect_stream = re.compile(r"^[12]?>") + re_cl_capture = re.compile(r"^.+\.(c|cc|cpp|cxx|c[+]{2})$", re.IGNORECASE) + re_link_capture = re.compile(r'\s{3}\S.+\s(?:"[^"]+.lib"|\S+.lib)\s.+\s(?:"[^"]+.exp"|\S+.exp)') + + def spawn_capture(sh, escape, cmd, args, env): + # We only care about cl/link, process everything else as normal. + if args[0] not in ["cl", "link"]: + return old_spawn(sh, escape, cmd, args, env) + + # Process as normal if the user is manually rerouting output. + for arg in args: + if re_redirect_stream.match(arg): + return old_spawn(sh, escape, cmd, args, env) + + tmp_stdout, tmp_stdout_name = tempfile.mkstemp() + os.close(tmp_stdout) + args.append(f">{tmp_stdout_name}") + ret = old_spawn(sh, escape, cmd, args, env) + + try: + with open(tmp_stdout_name, "r", encoding=sys.stdout.encoding, errors="replace") as tmp_stdout: + lines = tmp_stdout.read().splitlines() + os.remove(tmp_stdout_name) + except OSError: + pass + + # Early process no lines (OSError) + if not lines: + return ret + + is_cl = args[0] == "cl" + content = "" + caught = False + for line in lines: + # These conditions are far from all-encompassing, but are specialized + # for what can be reasonably expected to show up in the repository. + if not caught and (is_cl and re_cl_capture.match(line)) or (not is_cl and re_link_capture.match(line)): + caught = True + try: + with open(capture_path, "a", encoding=sys.stdout.encoding) as log: + log.write(line + "\n") + except OSError: + print(f'WARNING: Failed to log captured line: "{line}".') + continue + content += line + "\n" + # Content remaining assumed to be an error/warning. + if content: + sys.stderr.write(content) + + return ret + + env["SPAWN"] = spawn_capture + + +def options(opts): + mingw = os.getenv("MINGW_PREFIX", "") + + 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) + + +def exists(env): + return True + + +def generate(env): + env["lto"] = "none" # Patched to work with the older godotcpp tool in webrtc-native. + + if not env["use_mingw"] and msvc.exists(env): + if env["arch"] == "x86_64": + env["TARGET_ARCH"] = "amd64" + elif env["arch"] == "arm64": + env["TARGET_ARCH"] = "arm64" + elif env["arch"] == "arm32": + env["TARGET_ARCH"] = "arm" + elif env["arch"] == "x86_32": + env["TARGET_ARCH"] = "x86" + + env["MSVC_SETUP_RUN"] = False # Need to set this to re-run the tool + env["MSVS_VERSION"] = None + env["MSVC_VERSION"] = None + + env["is_msvc"] = True + + # MSVC, linker, and archiver. + msvc.generate(env) + env.Tool("msvc") + env.Tool("mslib") + env.Tool("mslink") + + env.Append(CPPDEFINES=["TYPED_METHOD_BIND", "NOMINMAX"]) + env.Append(CCFLAGS=["/utf-8"]) + env.Append(LINKFLAGS=["/WX"]) + + if env["use_llvm"]: + env["CC"] = "clang-cl" + env["CXX"] = "clang-cl" + + if env["debug_crt"]: + # Always use dynamic runtime, static debug CRT breaks thread_local. + env.AppendUnique(CCFLAGS=["/MDd"]) + else: + 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) + + if not env["use_llvm"]: + env.AppendUnique(CCFLAGS=["/experimental:external", "/external:anglebrackets"]) + env.AppendUnique(CCFLAGS=["/external:W0"]) + env["EXTINCPREFIX"] = "/external:I" + + elif (sys.platform == "win32" or sys.platform == "msys") and not env["mingw_prefix"]: + env["use_mingw"] = True + mingw.generate(env) + # Don't want lib prefixes + env["IMPLIBPREFIX"] = "" + 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) + + else: + env["use_mingw"] = True + # Cross-compilation using MinGW + prefix = "" + if env["mingw_prefix"]: + prefix = env["mingw_prefix"] + "/bin/" + + if env["arch"] == "x86_64": + prefix += "x86_64" + elif env["arch"] == "arm64": + prefix += "aarch64" + elif env["arch"] == "arm32": + prefix += "armv7" + elif env["arch"] == "x86_32": + prefix += "i686" + + if env["use_llvm"]: + env["CXX"] = prefix + "-w64-mingw32-clang++" + env["CC"] = prefix + "-w64-mingw32-clang" + env["AR"] = prefix + "-w64-mingw32-llvm-ar" + env["RANLIB"] = prefix + "-w64-mingw32-ranlib" + env["LINK"] = prefix + "-w64-mingw32-clang" + else: + env["CXX"] = prefix + "-w64-mingw32-g++" + env["CC"] = prefix + "-w64-mingw32-gcc" + env["AR"] = prefix + "-w64-mingw32-gcc-ar" + env["RANLIB"] = prefix + "-w64-mingw32-ranlib" + env["LINK"] = prefix + "-w64-mingw32-g++" + + # 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++", + ] + ) + if env["use_llvm"]: + env.Append(LINKFLAGS=["-lstdc++"]) + + if sys.platform == "win32" or sys.platform == "msys": + my_spawn.configure(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) diff --git a/misc/scripts/package_release.sh b/misc/scripts/package_release.sh index c3cf553..9a0f3a7 100755 --- a/misc/scripts/package_release.sh +++ b/misc/scripts/package_release.sh @@ -27,6 +27,8 @@ fi CURDIR=$(pwd) cd "${DESTINATION}/${VERSION}" +# Clear unneded windows files +rm ${TYPE}/lib/*.pdb ${TYPE}/lib/*.exp ${TYPE}/lib/*.lib || echo "Nothing to delete" zip -r ../godot-${VERSION}-${TYPE}.zip ${TYPE} cd "$CURDIR" diff --git a/misc/webrtc.gdextension b/misc/webrtc.gdextension index a112463..9017693 100644 --- a/misc/webrtc.gdextension +++ b/misc/webrtc.gdextension @@ -12,6 +12,7 @@ linux.debug.arm32 = "lib/libwebrtc_native.linux.template_debug.arm32.so" macos.debug = "lib/libwebrtc_native.macos.template_debug.universal.framework" windows.debug.x86_64 = "lib/libwebrtc_native.windows.template_debug.x86_64.dll" windows.debug.x86_32 = "lib/libwebrtc_native.windows.template_debug.x86_32.dll" +windows.debug.arm64 = "lib/libwebrtc_native.windows.template_debug.arm64.dll" android.debug.arm64 = "lib/libwebrtc_native.android.template_debug.arm64.so" android.debug.x86_64 = "lib/libwebrtc_native.android.template_debug.x86_64.so" ios.debug.arm64 = "lib/libwebrtc_native.ios.template_debug.arm64.dylib" @@ -24,6 +25,7 @@ linux.release.arm32 = "lib/libwebrtc_native.linux.template_release.arm32.so" macos.release = "lib/libwebrtc_native.macos.template_release.universal.framework" windows.release.x86_64 = "lib/libwebrtc_native.windows.template_release.x86_64.dll" windows.release.x86_32 = "lib/libwebrtc_native.windows.template_release.x86_32.dll" +windows.release.arm64 = "lib/libwebrtc_native.windows.template_release.arm64.dll" android.release.arm64 = "lib/libwebrtc_native.android.template_release.arm64.so" android.release.x86_64 = "lib/libwebrtc_native.android.template_release.x86_64.so" ios.release.arm64 = "lib/libwebrtc_native.ios.template_release.arm64.dylib"