Browse Source

Merge pull request #86255 from bruvzg/_bundle_build

[iOS/macOS] Add option to automatically build (and sign / archive) bundles.
Rémi Verschelde 1 year ago
parent
commit
de77f0ac7f

+ 0 - 1
SConstruct

@@ -182,7 +182,6 @@ opts.Add(BoolVariable("debug_symbols", "Build with debugging symbols", False))
 opts.Add(BoolVariable("separate_debug_symbols", "Extract debugging symbols to a separate file", False))
 opts.Add(EnumVariable("lto", "Link-time optimization (production builds)", "none", ("none", "auto", "thin", "full")))
 opts.Add(BoolVariable("production", "Set defaults to build Godot for use in production", False))
-opts.Add(BoolVariable("generate_apk", "Generate an APK/AAB after building Android library by calling Gradle", False))
 opts.Add(BoolVariable("threads", "Enable threading support", True))
 
 # Components

+ 22 - 0
misc/dist/macos/editor_debug.entitlements

@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+ <key>com.apple.security.cs.allow-dyld-environment-variables</key>
+ <true/>
+ <key>com.apple.security.cs.allow-jit</key>
+ <true/>
+ <key>com.apple.security.cs.allow-unsigned-executable-memory</key>
+ <true/>
+ <key>com.apple.security.cs.disable-executable-page-protection</key>
+ <true/>
+ <key>com.apple.security.cs.disable-library-validation</key>
+ <true/>
+ <key>com.apple.security.device.audio-input</key>
+ <true/>
+ <key>com.apple.security.device.camera</key>
+ <true/>
+ <key>com.apple.security.get-task-allow</key>
+ <true/>
+</dict>
+</plist>

+ 199 - 0
misc/dist/macos/editor_info_plist.template

@@ -0,0 +1,199 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+	<key>CFBundleDevelopmentRegion</key>
+	<string>English</string>
+	<key>CFBundleExecutable</key>
+	<string>Godot</string>
+	<key>CFBundleName</key>
+	<string>Godot</string>
+	<key>CFBundleIconFile</key>
+	<string>Godot.icns</string>
+	<key>CFBundleIdentifier</key>
+	<string>org.godotengine.godot</string>
+	<key>CFBundleInfoDictionaryVersion</key>
+	<string>6.0</string>
+	<key>CFBundlePackageType</key>
+	<string>APPL</string>
+	<key>CFBundleShortVersionString</key>
+	<string>$short_version</string>
+	<key>CFBundleSignature</key>
+	<string>godot</string>
+	<key>CFBundleVersion</key>
+	<string>$version</string>
+	<key>NSMicrophoneUsageDescription</key>
+	<string>Microphone access is required to capture audio.</string>
+	<key>NSCameraUsageDescription</key>
+	<string>Camera access is required to capture video.</string>
+	<key>NSRequiresAquaSystemAppearance</key>
+	<false/>
+	<key>NSHumanReadableCopyright</key>
+	<string>© 2007-present Juan Linietsky, Ariel Manzur &amp; Godot Engine contributors</string>
+	<key>CFBundleSupportedPlatforms</key>
+	<array>
+		<string>MacOSX</string>
+	</array>
+	<key>NSPrincipalClass</key>
+	<string>NSApplication</string>
+	<key>LSApplicationCategoryType</key>
+	<string>public.app-category.developer-tools</string>
+	<key>LSMinimumSystemVersion</key>
+	<string>10.12</string>
+	<key>LSMinimumSystemVersionByArchitecture</key>
+	<dict>
+		<key>x86_64</key>
+		<string>10.12</string>
+	</dict>
+	<key>NSHighResolutionCapable</key>
+	<true/>
+	<key>CFBundleDocumentTypes</key>
+	<array>
+		<dict>
+			<key>CFBundleTypeRole</key>
+			<string>Editor</string>
+			<key>LSItemContentTypes</key>
+			<array>
+				<string>public.tscn</string>
+			</array>
+			<key>NSExportableTypes</key>
+			<array>
+				<string>public.tscn</string>
+			</array>
+		</dict>
+		<dict>
+			<key>CFBundleTypeRole</key>
+			<string>Editor</string>
+			<key>LSItemContentTypes</key>
+			<array>
+				<string>public.godot</string>
+			</array>
+			<key>NSExportableTypes</key>
+			<array>
+				<string>public.godot</string>
+			</array>
+		</dict>
+	</array>
+	<key>UTExportedTypeDeclarations</key>
+	<array>
+		<dict>
+			<key>UTTypeIdentifier</key>
+			<string>public.tscn</string>
+			<key>UTTypeReferenceURL</key>
+			<string></string>
+			<key>UTTypeDescription</key>
+			<string>Godot Engine scene</string>
+			<key>UTTypeIconFile</key>
+			<string>Scene.icns</string>
+			<key>UTTypeConformsTo</key>
+			<array>
+				<string>public.data</string>
+			</array>
+			<key>UTTypeTagSpecification</key>
+			<dict>
+				<key>public.filename-extension</key>
+				<array>
+					<string>scn</string>
+					<string>tscn</string>
+					<string>escn</string>
+				</array>
+				<key>public.mime-type</key>
+				<string>application/x-godot-scene</string>
+			</dict>
+		</dict>
+		<dict>
+			<key>UTTypeIdentifier</key>
+			<string>public.gd</string>
+			<key>UTTypeReferenceURL</key>
+			<string></string>
+			<key>UTTypeDescription</key>
+			<string>GDScript script</string>
+			<key>UTTypeIconFile</key>
+			<string>GDScript.icns</string>
+			<key>UTTypeConformsTo</key>
+			<array>
+				<string>public.script</string>
+			</array>
+			<key>UTTypeTagSpecification</key>
+			<dict>
+				<key>public.filename-extension</key>
+				<array>
+					<string>gd</string>
+				</array>
+				<key>public.mime-type</key>
+				<string>application/x-gdscript</string>
+			</dict>
+		</dict>
+		<dict>
+			<key>UTTypeIdentifier</key>
+			<string>public.res</string>
+			<key>UTTypeReferenceURL</key>
+			<string></string>
+			<key>UTTypeDescription</key>
+			<string>Godot Engine resource</string>
+			<key>UTTypeIconFile</key>
+			<string>Resource.icns</string>
+			<key>UTTypeConformsTo</key>
+			<array>
+				<string>public.data</string>
+			</array>
+			<key>UTTypeTagSpecification</key>
+			<dict>
+				<key>public.filename-extension</key>
+				<array>
+					<string>res</string>
+					<string>tres</string>
+				</array>
+				<key>public.mime-type</key>
+				<string>application/x-godot-resource</string>
+			</dict>
+		</dict>
+		<dict>
+			<key>UTTypeIdentifier</key>
+			<string>public.gdshader</string>
+			<key>UTTypeReferenceURL</key>
+			<string></string>
+			<key>UTTypeDescription</key>
+			<string>Godot Engine shader</string>
+			<key>UTTypeIconFile</key>
+			<string>Shader.icns</string>
+			<key>UTTypeConformsTo</key>
+			<array>
+				<string>public.script</string>
+			</array>
+			<key>UTTypeTagSpecification</key>
+			<dict>
+				<key>public.filename-extension</key>
+				<array>
+					<string>gdshader</string>
+				</array>
+				<key>public.mime-type</key>
+				<string>application/x-godot-shader</string>
+			</dict>
+		</dict>
+		<dict>
+			<key>UTTypeIdentifier</key>
+			<string>public.godot</string>
+			<key>UTTypeReferenceURL</key>
+			<string></string>
+			<key>UTTypeDescription</key>
+			<string>Godot Engine project</string>
+			<key>UTTypeIconFile</key>
+			<string>Project.icns</string>
+			<key>UTTypeConformsTo</key>
+			<array>
+				<string>public.data</string>
+			</array>
+			<key>UTTypeTagSpecification</key>
+			<dict>
+				<key>public.filename-extension</key>
+				<array>
+					<string>godot</string>
+				</array>
+				<key>public.mime-type</key>
+				<string>application/x-godot-project</string>
+			</dict>
+		</dict>
+	</array>
+</dict>
+</plist>

+ 1 - 0
platform/android/detect.py

@@ -28,6 +28,7 @@ def get_opts():
             "android-" + str(get_min_target_api()),
         ),
         BoolVariable("store_release", "Editor build for Google Play Store (for official builds only)", False),
+        BoolVariable("generate_apk", "Generate an APK/AAB after building Android library by calling Gradle", False),
     ]
 
 

+ 61 - 0
platform/ios/SCsub

@@ -2,6 +2,62 @@
 
 Import("env")
 
+import os, json
+from platform_methods import run_in_subprocess, architectures, lipo, get_build_version, detect_mvk
+import subprocess
+import shutil
+
+
+def generate_bundle(target, source, env):
+    bin_dir = Dir("#bin").abspath
+
+    # Template bundle.
+    app_prefix = "godot." + env["platform"]
+    rel_prefix = "libgodot." + env["platform"] + "." + "template_release"
+    dbg_prefix = "libgodot." + env["platform"] + "." + "template_debug"
+    if env.dev_build:
+        app_prefix += ".dev"
+        rel_prefix += ".dev"
+        dbg_prefix += ".dev"
+    if env["precision"] == "double":
+        app_prefix += ".double"
+        rel_prefix += ".double"
+        dbg_prefix += ".double"
+
+    # Lipo template libraries.
+    rel_target_bin = lipo(bin_dir + "/" + rel_prefix, env.extra_suffix + ".a")
+    dbg_target_bin = lipo(bin_dir + "/" + dbg_prefix, env.extra_suffix + ".a")
+    rel_target_bin_sim = lipo(bin_dir + "/" + rel_prefix, ".simulator" + env.extra_suffix + ".a")
+    dbg_target_bin_sim = lipo(bin_dir + "/" + dbg_prefix, ".simulator" + env.extra_suffix + ".a")
+
+    # Assemble Xcode project bundle.
+    app_dir = Dir("#bin/ios_xcode").abspath
+    templ = Dir("#misc/dist/ios_xcode").abspath
+    if os.path.exists(app_dir):
+        shutil.rmtree(app_dir)
+    shutil.copytree(templ, app_dir)
+    if rel_target_bin != "":
+        shutil.copy(rel_target_bin, app_dir + "/libgodot.ios.release.xcframework/ios-arm64/libgodot.a")
+    if dbg_target_bin != "":
+        shutil.copy(dbg_target_bin, app_dir + "/libgodot.ios.debug.xcframework/ios-arm64/libgodot.a")
+    if rel_target_bin_sim != "":
+        shutil.copy(
+            rel_target_bin_sim, app_dir + "/libgodot.ios.release.xcframework/ios-arm64_x86_64-simulator/libgodot.a"
+        )
+    if dbg_target_bin_sim != "":
+        shutil.copy(
+            dbg_target_bin_sim, app_dir + "/libgodot.ios.debug.xcframework/ios-arm64_x86_64-simulator/libgodot.a"
+        )
+    mvk_path = detect_mvk(env, "ios-arm64")
+    if mvk_path != "":
+        shutil.copytree(mvk_path, app_dir + "/MoltenVK.xcframework")
+
+    # ZIP Xcode project bundle.
+    zip_dir = Dir("#bin/" + (app_prefix + env.extra_suffix).replace(".", "_")).abspath
+    shutil.make_archive(zip_dir, "zip", root_dir=app_dir)
+    shutil.rmtree(app_dir)
+
+
 ios_lib = [
     "godot_ios.mm",
     "os_ios.mm",
@@ -42,3 +98,8 @@ def combine_libs(target=None, source=None, env=None):
 
 
 combine_command = env_ios.Command("#bin/libgodot" + env_ios["LIBSUFFIX"], [ios_lib] + env_ios["LIBS"], combine_libs)
+
+if env["generate_bundle"]:
+    generate_bundle_command = env.Command("generate_bundle", [], generate_bundle)
+    command = env.AlwaysBuild(generate_bundle_command)
+    env.Depends(command, [combine_command])

+ 2 - 0
platform/ios/detect.py

@@ -23,6 +23,7 @@ def get_opts():
     from SCons.Variables import BoolVariable
 
     return [
+        ("vulkan_sdk_path", "Path to the Vulkan SDK", ""),
         (
             "IOS_TOOLCHAIN_PATH",
             "Path to iOS toolchain",
@@ -31,6 +32,7 @@ def get_opts():
         ("IOS_SDK_PATH", "Path to the iOS SDK", ""),
         BoolVariable("ios_simulator", "Build for iOS Simulator", False),
         ("ios_triple", "Triple for ios toolchain", ""),
+        BoolVariable("generate_bundle", "Generate an APP bundle after building iOS/macOS binaries", False),
     ]
 
 

+ 97 - 1
platform/macos/SCsub

@@ -2,8 +2,99 @@
 
 Import("env")
 
-from platform_methods import run_in_subprocess
+import os, json
+from platform_methods import run_in_subprocess, architectures, lipo, get_build_version
 import platform_macos_builders
+import subprocess
+import shutil
+
+
+def generate_bundle(target, source, env):
+    bin_dir = Dir("#bin").abspath
+
+    if env.editor_build:
+        # Editor bundle.
+        prefix = "godot." + env["platform"] + "." + env["target"]
+        if env.dev_build:
+            prefix += ".dev"
+        if env["precision"] == "double":
+            prefix += ".double"
+
+        # Lipo editor executable.
+        target_bin = lipo(bin_dir + "/" + prefix, env.extra_suffix)
+
+        # Assemble .app bundle and update version info.
+        app_dir = Dir("#bin/" + (prefix + env.extra_suffix).replace(".", "_") + ".app").abspath
+        templ = Dir("#misc/dist/macos_tools.app").abspath
+        if os.path.exists(app_dir):
+            shutil.rmtree(app_dir)
+        shutil.copytree(templ, app_dir, ignore=shutil.ignore_patterns("Contents/Info.plist"))
+        if not os.path.isdir(app_dir + "/Contents/MacOS"):
+            os.mkdir(app_dir + "/Contents/MacOS")
+        if target_bin != "":
+            shutil.copy(target_bin, app_dir + "/Contents/MacOS/Godot")
+        version = get_build_version(False)
+        short_version = get_build_version(True)
+        with open(Dir("#misc/dist/macos").abspath + "/editor_info_plist.template", "rt") as fin:
+            with open(app_dir + "/Contents/Info.plist", "wt") as fout:
+                for line in fin:
+                    line = line.replace("$version", version)
+                    line = line.replace("$short_version", short_version)
+                    fout.write(line)
+
+        # Sign .app bundle.
+        if env["bundle_sign_identity"] != "":
+            sign_command = [
+                "codesign",
+                "-s",
+                env["bundle_sign_identity"],
+                "--deep",
+                "--force",
+                "--options=runtime",
+                "--entitlements",
+            ]
+            if env.dev_build:
+                sign_command += [Dir("#misc/dist/macos").abspath + "/editor_debug.entitlements"]
+            else:
+                sign_command += [Dir("#misc/dist/macos").abspath + "/editor.entitlements"]
+            sign_command += [app_dir]
+            subprocess.run(sign_command)
+    else:
+        # Template bundle.
+        app_prefix = "godot." + env["platform"]
+        rel_prefix = "godot." + env["platform"] + "." + "template_release"
+        dbg_prefix = "godot." + env["platform"] + "." + "template_debug"
+        if env.dev_build:
+            app_prefix += ".dev"
+            rel_prefix += ".dev"
+            dbg_prefix += ".dev"
+        if env["precision"] == "double":
+            app_prefix += ".double"
+            rel_prefix += ".double"
+            dbg_prefix += ".double"
+
+        # Lipo template executables.
+        rel_target_bin = lipo(bin_dir + "/" + rel_prefix, env.extra_suffix)
+        dbg_target_bin = lipo(bin_dir + "/" + dbg_prefix, env.extra_suffix)
+
+        # Assemble .app bundle.
+        app_dir = Dir("#bin/macos_template.app").abspath
+        templ = Dir("#misc/dist/macos_template.app").abspath
+        if os.path.exists(app_dir):
+            shutil.rmtree(app_dir)
+        shutil.copytree(templ, app_dir)
+        if not os.path.isdir(app_dir + "/Contents/MacOS"):
+            os.mkdir(app_dir + "/Contents/MacOS")
+        if rel_target_bin != "":
+            shutil.copy(rel_target_bin, app_dir + "/Contents/MacOS/godot_macos_release.universal")
+        if dbg_target_bin != "":
+            shutil.copy(dbg_target_bin, app_dir + "/Contents/MacOS/godot_macos_debug.universal")
+
+        # ZIP .app bundle.
+        zip_dir = Dir("#bin/" + (app_prefix + env.extra_suffix).replace(".", "_")).abspath
+        shutil.make_archive(zip_dir, "zip", root_dir=bin_dir, base_dir="macos_template.app")
+        shutil.rmtree(app_dir)
+
 
 files = [
     "os_macos.mm",
@@ -34,3 +125,8 @@ prog = env.add_program("#bin/godot", files)
 
 if env["debug_symbols"] and env["separate_debug_symbols"]:
     env.AddPostAction(prog, run_in_subprocess(platform_macos_builders.make_debug_macos))
+
+if env["generate_bundle"]:
+    generate_bundle_command = env.Command("generate_bundle", [], generate_bundle)
+    command = env.AlwaysBuild(generate_bundle_command)
+    env.Depends(command, [prog])

+ 11 - 67
platform/macos/detect.py

@@ -1,7 +1,7 @@
 import os
 import sys
 from methods import detect_darwin_sdk_path, get_compiler_version, is_vanilla_clang
-from platform_methods import detect_arch
+from platform_methods import detect_arch, detect_mvk
 
 from typing import TYPE_CHECKING
 
@@ -33,6 +33,12 @@ def get_opts():
         BoolVariable("use_tsan", "Use LLVM/GCC compiler thread sanitizer (TSAN)", False),
         BoolVariable("use_coverage", "Use instrumentation codes in the binary (e.g. for code coverage)", False),
         ("angle_libs", "Path to the ANGLE static libraries", ""),
+        (
+            "bundle_sign_identity",
+            "The 'Full Name', 'Common Name' or SHA-1 hash of the signing identity used to sign editor .app bundle.",
+            "-",
+        ),
+        BoolVariable("generate_bundle", "Generate an APP bundle after building iOS/macOS binaries", False),
     ]
 
 
@@ -53,47 +59,6 @@ def get_flags():
     ]
 
 
-def get_mvk_sdk_path():
-    def int_or_zero(i):
-        try:
-            return int(i)
-        except:
-            return 0
-
-    def ver_parse(a):
-        return [int_or_zero(i) for i in a.split(".")]
-
-    dirname = os.path.expanduser("~/VulkanSDK")
-    if not os.path.exists(dirname):
-        return ""
-
-    ver_min = ver_parse("1.3.231.0")
-    ver_num = ver_parse("0.0.0.0")
-    files = os.listdir(dirname)
-    lib_name_out = dirname
-    for file in files:
-        if os.path.isdir(os.path.join(dirname, file)):
-            ver_comp = ver_parse(file)
-            if ver_comp > ver_num and ver_comp >= ver_min:
-                # Try new SDK location.
-                lib_name = os.path.join(
-                    os.path.join(dirname, file), "macOS/lib/MoltenVK.xcframework/macos-arm64_x86_64/"
-                )
-                if os.path.isfile(os.path.join(lib_name, "libMoltenVK.a")):
-                    ver_num = ver_comp
-                    lib_name_out = lib_name
-                else:
-                    # Try old SDK location.
-                    lib_name = os.path.join(
-                        os.path.join(dirname, file), "MoltenVK/MoltenVK.xcframework/macos-arm64_x86_64/"
-                    )
-                    if os.path.isfile(os.path.join(lib_name, "libMoltenVK.a")):
-                        ver_num = ver_comp
-                        lib_name_out = lib_name
-
-    return lib_name_out
-
-
 def configure(env: "Environment"):
     # Validate arch.
     supported_arches = ["x86_64", "arm64"]
@@ -274,32 +239,11 @@ def configure(env: "Environment"):
         env.Append(LINKFLAGS=["-framework", "Metal", "-framework", "IOSurface"])
         if not env["use_volk"]:
             env.Append(LINKFLAGS=["-lMoltenVK"])
-            mvk_found = False
-
-            mvk_list = [get_mvk_sdk_path(), "/opt/homebrew/lib", "/usr/local/homebrew/lib", "/opt/local/lib"]
-            if env["vulkan_sdk_path"] != "":
-                mvk_list.insert(0, os.path.expanduser(env["vulkan_sdk_path"]))
-                mvk_list.insert(
-                    0,
-                    os.path.join(
-                        os.path.expanduser(env["vulkan_sdk_path"]), "macOS/lib/MoltenVK.xcframework/macos-arm64_x86_64/"
-                    ),
-                )
-                mvk_list.insert(
-                    0,
-                    os.path.join(
-                        os.path.expanduser(env["vulkan_sdk_path"]), "MoltenVK/MoltenVK.xcframework/macos-arm64_x86_64/"
-                    ),
-                )
-
-            for mvk_path in mvk_list:
-                if mvk_path and os.path.isfile(os.path.join(mvk_path, "libMoltenVK.a")):
-                    mvk_found = True
-                    print("MoltenVK found at: " + mvk_path)
-                    env.Append(LINKFLAGS=["-L" + mvk_path])
-                    break
+            mvk_path = detect_mvk(env, "macos-arm64_x86_64")
 
-            if not mvk_found:
+            if mvk_path != "":
+                env.Append(LINKFLAGS=["-L" + os.path.join(mvk_path, "macos-arm64_x86_64")])
+            else:
                 print(
                     "MoltenVK SDK installation directory not found, use 'vulkan_sdk_path' SCons parameter to specify SDK path."
                 )

+ 105 - 0
platform_methods.py

@@ -140,3 +140,108 @@ def generate_export_icons(platform_path, platform_name):
         wf = export_path + "/" + name + "_svg.gen.h"
         with open(wf, "w") as svgw:
             svgw.write(svg_str)
+
+
+def get_build_version(short):
+    import version
+
+    name = "custom_build"
+    if os.getenv("BUILD_NAME") != None:
+        name = os.getenv("BUILD_NAME")
+    v = "%d.%d" % (version.major, version.minor)
+    if version.patch > 0:
+        v += ".%d" % version.patch
+    status = version.status
+    if not short:
+        if os.getenv("GODOT_VERSION_STATUS") != None:
+            status = str(os.getenv("GODOT_VERSION_STATUS"))
+        v += ".%s.%s" % (status, name)
+    return v
+
+
+def lipo(prefix, suffix):
+    from pathlib import Path
+
+    target_bin = ""
+    lipo_command = ["lipo", "-create"]
+    arch_found = 0
+
+    for arch in architectures:
+        bin_name = prefix + "." + arch + suffix
+        if Path(bin_name).is_file():
+            target_bin = bin_name
+            lipo_command += [bin_name]
+            arch_found += 1
+
+    if arch_found > 1:
+        target_bin = prefix + ".fat" + suffix
+        lipo_command += ["-output", target_bin]
+        subprocess.run(lipo_command)
+
+    return target_bin
+
+
+def get_mvk_sdk_path(osname):
+    def int_or_zero(i):
+        try:
+            return int(i)
+        except:
+            return 0
+
+    def ver_parse(a):
+        return [int_or_zero(i) for i in a.split(".")]
+
+    dirname = os.path.expanduser("~/VulkanSDK")
+    if not os.path.exists(dirname):
+        return ""
+
+    ver_min = ver_parse("1.3.231.0")
+    ver_num = ver_parse("0.0.0.0")
+    files = os.listdir(dirname)
+    lib_name_out = dirname
+    for file in files:
+        if os.path.isdir(os.path.join(dirname, file)):
+            ver_comp = ver_parse(file)
+            if ver_comp > ver_num and ver_comp >= ver_min:
+                # Try new SDK location.
+                lib_name = os.path.join(os.path.join(dirname, file), "macOS/lib/MoltenVK.xcframework/" + osname + "/")
+                if os.path.isfile(os.path.join(lib_name, "libMoltenVK.a")):
+                    ver_num = ver_comp
+                    lib_name_out = os.path.join(os.path.join(dirname, file), "macOS/lib/MoltenVK.xcframework")
+                else:
+                    # Try old SDK location.
+                    lib_name = os.path.join(
+                        os.path.join(dirname, file), "MoltenVK/MoltenVK.xcframework/" + osname + "/"
+                    )
+                    if os.path.isfile(os.path.join(lib_name, "libMoltenVK.a")):
+                        ver_num = ver_comp
+                        lib_name_out = os.path.join(os.path.join(dirname, file), "MoltenVK/MoltenVK.xcframework")
+
+    return lib_name_out
+
+
+def detect_mvk(env, osname):
+    mvk_list = [
+        get_mvk_sdk_path(osname),
+        "/opt/homebrew/Frameworks/MoltenVK.xcframework",
+        "/usr/local/homebrew/Frameworks/MoltenVK.xcframework",
+        "/opt/local/Frameworks/MoltenVK.xcframework",
+    ]
+    if env["vulkan_sdk_path"] != "":
+        mvk_list.insert(0, os.path.expanduser(env["vulkan_sdk_path"]))
+        mvk_list.insert(
+            0,
+            os.path.join(os.path.expanduser(env["vulkan_sdk_path"]), "macOS/lib/MoltenVK.xcframework"),
+        )
+        mvk_list.insert(
+            0,
+            os.path.join(os.path.expanduser(env["vulkan_sdk_path"]), "MoltenVK/MoltenVK.xcframework"),
+        )
+
+    for mvk_path in mvk_list:
+        if mvk_path and os.path.isfile(os.path.join(mvk_path, osname + "/libMoltenVK.a")):
+            mvk_found = True
+            print("MoltenVK found at: " + mvk_path)
+            return mvk_path
+
+    return ""