diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 123fbbb..e8ca387 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -12,11 +12,22 @@ on: jobs: build: - name: Build on ${{ matrix.os }} runs-on: ${{ matrix.os }} strategy: matrix: - os: [ubuntu-latest, windows-latest] + include: + - platform: linux + arch: x86_64 + os: ubuntu-latest + - platform: windows + arch: x86_64 + os: windows-latest + - platform: macos + arch: x86_64 + os: macos-latest + - platform: macos + arch: arm64 + os: macos-latest steps: - name: Checkout repository @@ -39,31 +50,31 @@ jobs: - name: Build extension (Linux) if: matrix.os == 'ubuntu-latest' run: | - scons platform=linux arch=x86_64 single_source=true + scons platform=${{ matrix.platform }} arch=${{ matrix.arch }} single_source=true - name: Build extension (Windows) if: matrix.os == 'windows-latest' shell: pwsh run: | - scons platform=windows arch=x86_64 single_source=true + scons platform=${{ matrix.platform }} arch=${{ matrix.arch }} single_source=true - name: Build extension (macOS) if: matrix.os == 'macos-latest' run: | - scons platform=macos arch=universal single_source=true + scons platform=${{ matrix.platform }} arch=${{ matrix.arch }} single_source=true - name: Create archive (Linux) if: matrix.os == 'ubuntu-latest' run: | cd bin/ - zip -q -r ../godot-python-linux-x86_64.zip * + zip -q -r ../godot-python-${{ matrix.platform }}-${{ matrix.arch }}.zip * cd ../ - name: Upload artifacts (Linux) if: matrix.os == 'ubuntu-latest' uses: actions/upload-artifact@v3 with: - name: godot-python-linux-x86_64 + name: godot-python-${{ matrix.platform }}-${{ matrix.arch }} path: godot-python*.zip retention-days: 30 @@ -71,7 +82,7 @@ jobs: if: matrix.os == 'windows-latest' uses: actions/upload-artifact@v3 with: - name: godot-python-windows-x86_64 + name: godot-python-${{ matrix.platform }}-${{ matrix.arch }} path: | bin/**/* !bin/**/*.lib @@ -82,11 +93,8 @@ jobs: if: matrix.os == 'macos-latest' uses: actions/upload-artifact@v3 with: - name: godot-python-macos-universal - path: | - bin/**/* - !bin/**/*.lib - !bin/**/*.exp + name: godot-python-${{ matrix.platform }}-${{ matrix.arch }} + path: bin/**/* retention-days: 30 - name: Release artifact diff --git a/SConstruct b/SConstruct index f1215b8..cafc9c2 100644 --- a/SConstruct +++ b/SConstruct @@ -199,6 +199,11 @@ python_sources = set() sources.update(pathlib.Path('src').glob('**/*.cpp')) +if env['platform'] == 'macos': + # For Objective-C++ files + env.Append(CCFLAGS = ['-ObjC++']) + sources.update(pathlib.Path('src').glob('**/*.mm')) + for ext in ('py', 'pyc', 'json', 'svg', 'md'): python_sources.update(pathlib.Path('lib').glob(f'**/*.{ext}')) @@ -340,7 +345,6 @@ strip = env.get('strip', False) if not env.get('is_msvc'): env.Append(CCFLAGS = ['-fvisibility=hidden', *['-flto'] * with_lto]) # XXX env.Append(LINKFLAGS = ['-fvisibility=hidden', *['-flto'] * with_lto, *['-s'] * strip]) # XXX - else: env.Append(LIBS = ['Shell32.lib', ]) @@ -348,6 +352,11 @@ else: if env['platform'] == 'windows': # linker has trouble if the table is too large env.Append(CPPDEFINES = ['CLASS_VIRTUAL_CALL_TABLE_SIZE=512']) +elif env['platform'] == 'macos': + # Need to set the rpath for relative loading of libpython to succeed. + # This doesn't work for some reason + # env.Replace(RPATH=['@loader_path']) + env.Append(LINKFLAGS=['-Wl,-rpath,@loader_path']) env.Prepend(CPPPATH=['src', os.fspath(generated_path), 'extern/pybind11/include']) diff --git a/src/util/macos.h b/src/util/macos.h new file mode 100644 index 0000000..510d23d --- /dev/null +++ b/src/util/macos.h @@ -0,0 +1,15 @@ +#ifndef MACOS_H +#define MACOS_H + +#include +#include +#include + +namespace macos { + +// get the full argv passed to the main process +std::vector get_argv(); + +} + +#endif //MACOS_H diff --git a/src/util/macos.mm b/src/util/macos.mm new file mode 100644 index 0000000..93c531a --- /dev/null +++ b/src/util/macos.mm @@ -0,0 +1,13 @@ +#include "util/macos.h" + +#import + + +std::vector macos::get_argv() { + std::vector argv; + NSArray *argv_objc = [[NSProcessInfo processInfo] arguments]; + for (NSString *arg in argv_objc) { + argv.push_back([arg UTF8String]); + } + return argv; +} diff --git a/src/util/system.cpp b/src/util/system.cpp index e034429..72a92f9 100644 --- a/src/util/system.cpp +++ b/src/util/system.cpp @@ -10,6 +10,13 @@ #ifdef UNIX_ENABLED #include #include +#include +#endif + +#ifdef MACOS_ENABLED +#include +#include +#include "util/macos.h" #endif #ifdef WINDOWS_ENABLED @@ -29,8 +36,10 @@ namespace pygodot { void system_quick_exit(int status) { #ifdef WINDOWS_ENABLED TerminateProcess(GetCurrentProcess(), status); -#else +#elif defined(_GLIBCXX_HAVE_QUICK_EXIT) std::quick_exit(status); +#else + std::_Exit(status); #endif } @@ -58,11 +67,22 @@ std::filesystem::path get_executable_path() { memset(buffer, 0, sizeof(buffer)); ssize_t len = readlink("/proc/self/exe", buffer, sizeof(buffer)); return std::filesystem::path(buffer); -#endif -#ifdef WINDOWS_ENABLED +#elif defined(MACOS_ENABLED) + char pathbuf[PROC_PIDPATHINFO_MAXSIZE]; + + const pid_t pid = getpid(); + const int ret = proc_pidpath(pid, pathbuf, sizeof(pathbuf)); + if (ret <= 0) { + fprintf(stderr, "PID %d: proc_pidpath ();\n", pid); + fprintf(stderr, " %s\n", strerror(errno)); + } + return std::filesystem::path(pathbuf); +#elif defined(WINDOWS_ENABLED) WCHAR buffer[4096]; GetModuleFileNameW(nullptr, buffer, 4096); return std::filesystem::path(buffer); +#else +#error System not supported. #endif } @@ -87,9 +107,9 @@ std::vector get_argv() { } fclose(cmdline_file); -#endif - -#ifdef WINDOWS_ENABLED +#elif defined(MACOS_ENABLED) + return macos::get_argv(); +#elif defined(WINDOWS_ENABLED) int num_args; LPWSTR* arg_list = CommandLineToArgvW(GetCommandLineW(), &num_args); @@ -105,6 +125,8 @@ std::vector get_argv() { args.emplace_back(arg); } } +#else +#error System not supported. #endif return args; diff --git a/test/python.gdextension b/test/python.gdextension index 9389be2..dc81dcd 100644 --- a/test/python.gdextension +++ b/test/python.gdextension @@ -5,7 +5,8 @@ entry_symbol = "python_extension_init" [libraries] -macos = "res://bin/macos/libgodot-python.macos.framework" +macos.x86_64 = "res://bin/macos-x86_64/libgodot-python.macos.x86_64.dylib" +macos.arm64 = "res://bin/macos-arm64/libgodot-python.macos.arm64.dylib" windows.x86_32 = "res://bin/windows-x86_32/libgodot-python.windows.x86_32.dll" windows.x86_64 = "res://bin/windows-x86_64/libgodot-python.windows.x86_64.dll" @@ -16,4 +17,3 @@ linux.rv64 = "res://bin/linux-rv64/libgodot-python.linux.rv64.so" android.x86_64 = "res://bin/android-x86_64/libgodot-python.android.x86_64.so" android.arm64 = "res://bin/android-arm64/libgodot-python.android.arm64.so" - diff --git a/tools/build/build_utils.py b/tools/build/build_utils.py index ef2bb72..058519d 100644 --- a/tools/build/build_utils.py +++ b/tools/build/build_utils.py @@ -124,9 +124,9 @@ def process_arch(env): # No architecture specified. Default to arm64 if building for Android, # universal if building for macOS or iOS, wasm32 if building for web, # otherwise default to the host architecture. - if env["platform"] in ["macos", "ios"]: - env["arch"] = "universal" - elif env["platform"] == "android": + # if env["platform"] in ["macos", "ios"]: + # env["arch"] = "universal" + if env["platform"] == "android": env["arch"] = "arm64" elif env["platform"] == "web": env["arch"] = "wasm32" diff --git a/tools/build/prepare_python.py b/tools/build/prepare_python.py index b277a18..651f36f 100644 --- a/tools/build/prepare_python.py +++ b/tools/build/prepare_python.py @@ -59,6 +59,32 @@ def add_platform_config(*args, **kwargs): executable = 'python.exe', ) +add_platform_config( + platform = 'macos', + arch = 'x86_64', + source_url = 'https://github.com/indygreg/python-build-standalone/releases/download/' + '20231002/cpython-3.12.0+20231002-x86_64-apple-darwin-install_only.tar.gz', + so_suffixes = ['.so', '.dylib'], + ext_suffixes = ['.so'], + so_path = 'lib/libpython3.12.dylib', + python_lib_dir = 'lib/python3.12', + python_ext_dir = 'lib/python3.12/lib-dynload', + executable = 'bin/python3.12', +) + +add_platform_config( + platform = 'macos', + arch = 'arm64', + source_url = 'https://github.com/indygreg/python-build-standalone/releases/download/' + '20231002/cpython-3.12.0+20231002-aarch64-apple-darwin-install_only.tar.gz', + so_suffixes = ['.so', '.dylib'], + ext_suffixes = ['.so'], + so_path = 'lib/libpython3.12.dylib', + python_lib_dir = 'lib/python3.12', + python_ext_dir = 'lib/python3.12/lib-dynload', + executable = 'bin/python3.12', +) + def fetch_python_for_platform(platform: str, arch: str, dest_dir: pathlib.Path): config = platform_configs[(platform, arch)] @@ -80,11 +106,21 @@ def prepare_for_platform(platform: str, arch: str, shutil.unpack_archive(src_dir / pathlib.Path(config.source_url).name, extract_dir = src_dir) src = src_dir / 'python' + src_lib_path = src / config.so_path + lib_filename = pathlib.Path(config.so_path).name + + if platform == 'macos': + # Rename the library id (which we depend on) to be in @rpath. + # (it defaults to /install/lib/) + subprocess.run(['install_name_tool', '-id', f'@rpath/{lib_filename}', src_lib_path], check=True) dest_dir.mkdir(parents=True, exist_ok=True) + shutil.copy2(src_lib_path, dest_dir) - shutil.copy2(src / config.so_path, dest_dir) - subprocess.run(['strip', '-s', str(dest_dir / pathlib.Path(config.so_path).name)], check=True) + if platform == 'macos': + subprocess.run(['strip', '-x', dest_dir / lib_filename], check=True) + else: + subprocess.run(['strip', '-s', dest_dir / lib_filename], check=True) if (src / config.python_ext_dir).exists(): dest_ext_dir = dest_dir / 'python3.12' / 'lib-dynload' diff --git a/tools/build/python_config.py b/tools/build/python_config.py index 99e8a3a..35d6d89 100644 --- a/tools/build/python_config.py +++ b/tools/build/python_config.py @@ -111,7 +111,7 @@ def normpath(path: str) -> str: config_vars.link_libs = _stable_unique([ *itertools.chain(*( - (v.removeprefix('-l') for v in value.split()) + (v.removeprefix('-l') for v in value.split() if v.startswith('-l')) for name in ('LIBS', 'SYSLIBS') if (value := sysconfig_vars.get(name)) )),