Skip to content

Commit 990d8eb

Browse files
committed
Add build configurations and GitHub runners for macOS (x86_64 and arm64). Closes #11.
1 parent 76c41f8 commit 990d8eb

File tree

9 files changed

+131
-28
lines changed

9 files changed

+131
-28
lines changed

.github/workflows/build.yml

Lines changed: 21 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,22 @@ on:
1212

1313
jobs:
1414
build:
15-
name: Build on ${{ matrix.os }}
1615
runs-on: ${{ matrix.os }}
1716
strategy:
1817
matrix:
19-
os: [ubuntu-latest, windows-latest]
18+
include:
19+
- platform: linux
20+
arch: x86_64
21+
os: ubuntu-latest
22+
- platform: windows
23+
arch: x86_64
24+
os: windows-latest
25+
- platform: macos
26+
arch: x86_64
27+
os: macos-latest
28+
- platform: macos
29+
arch: arm64
30+
os: macos-latest
2031

2132
steps:
2233
- name: Checkout repository
@@ -39,39 +50,39 @@ jobs:
3950
- name: Build extension (Linux)
4051
if: matrix.os == 'ubuntu-latest'
4152
run: |
42-
scons platform=linux arch=x86_64 single_source=true
53+
scons platform=${{ matrix.platform }} arch=${{ matrix.arch }} single_source=true
4354
4455
- name: Build extension (Windows)
4556
if: matrix.os == 'windows-latest'
4657
shell: pwsh
4758
run: |
48-
scons platform=windows arch=x86_64 single_source=true
59+
scons platform=${{ matrix.platform }} arch=${{ matrix.arch }} single_source=true
4960
5061
- name: Build extension (macOS)
5162
if: matrix.os == 'macos-latest'
5263
run: |
53-
scons platform=macos arch=universal single_source=true
64+
scons platform=${{ matrix.platform }} arch=${{ matrix.arch }} single_source=true
5465
5566
- name: Create archive (Linux)
5667
if: matrix.os == 'ubuntu-latest'
5768
run: |
5869
cd bin/
59-
zip -q -r ../godot-python-linux-x86_64.zip *
70+
zip -q -r ../godot-python-${{ matrix.platform }}-${{ matrix.arch }}.zip *
6071
cd ../
6172
6273
- name: Upload artifacts (Linux)
6374
if: matrix.os == 'ubuntu-latest'
6475
uses: actions/upload-artifact@v3
6576
with:
66-
name: godot-python-linux-x86_64
77+
name: godot-python-${{ matrix.platform }}-${{ matrix.arch }}
6778
path: godot-python*.zip
6879
retention-days: 30
6980

7081
- name: Upload artifacts (Windows)
7182
if: matrix.os == 'windows-latest'
7283
uses: actions/upload-artifact@v3
7384
with:
74-
name: godot-python-windows-x86_64
85+
name: godot-python-${{ matrix.platform }}-${{ matrix.arch }}
7586
path: |
7687
bin/**/*
7788
!bin/**/*.lib
@@ -82,11 +93,8 @@ jobs:
8293
if: matrix.os == 'macos-latest'
8394
uses: actions/upload-artifact@v3
8495
with:
85-
name: godot-python-macos-universal
86-
path: |
87-
bin/**/*
88-
!bin/**/*.lib
89-
!bin/**/*.exp
96+
name: godot-python-${{ matrix.platform }}-${{ matrix.arch }}
97+
path: bin/**/*
9098
retention-days: 30
9199

92100
- name: Release artifact

SConstruct

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,11 @@ python_sources = set()
199199

200200
sources.update(pathlib.Path('src').glob('**/*.cpp'))
201201

202+
if env['platform'] == 'macos':
203+
# For Objective-C++ files
204+
env.Append(CCFLAGS = ['-ObjC++'])
205+
sources.update(pathlib.Path('src').glob('**/*.mm'))
206+
202207
for ext in ('py', 'pyc', 'json', 'svg', 'md'):
203208
python_sources.update(pathlib.Path('lib').glob(f'**/*.{ext}'))
204209

@@ -340,14 +345,18 @@ strip = env.get('strip', False)
340345
if not env.get('is_msvc'):
341346
env.Append(CCFLAGS = ['-fvisibility=hidden', *['-flto'] * with_lto]) # XXX
342347
env.Append(LINKFLAGS = ['-fvisibility=hidden', *['-flto'] * with_lto, *['-s'] * strip]) # XXX
343-
344348
else:
345349
env.Append(LIBS = ['Shell32.lib', ])
346350

347351

348352
if env['platform'] == 'windows':
349353
# linker has trouble if the table is too large
350354
env.Append(CPPDEFINES = ['CLASS_VIRTUAL_CALL_TABLE_SIZE=512'])
355+
elif env['platform'] == 'macos':
356+
# Need to set the rpath for relative loading of libpython to succeed.
357+
# This doesn't work for some reason
358+
# env.Replace(RPATH=['@loader_path'])
359+
env.Append(LINKFLAGS=['-Wl,-rpath,@loader_path'])
351360

352361

353362
env.Prepend(CPPPATH=['src', os.fspath(generated_path), 'extern/pybind11/include'])

src/util/macos.h

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
#ifndef MACOS_H
2+
#define MACOS_H
3+
4+
#include <vector>
5+
#include <string>
6+
#include <filesystem>
7+
8+
namespace macos {
9+
10+
// get the full argv passed to the main process
11+
std::vector<std::string> get_argv();
12+
13+
}
14+
15+
#endif //MACOS_H

src/util/macos.mm

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
#include "util/macos.h"
2+
3+
#import <Foundation/Foundation.h>
4+
5+
6+
std::vector<std::string> macos::get_argv() {
7+
std::vector<std::string> argv;
8+
NSArray *argv_objc = [[NSProcessInfo processInfo] arguments];
9+
for (NSString *arg in argv_objc) {
10+
argv.push_back([arg UTF8String]);
11+
}
12+
return argv;
13+
}

src/util/system.cpp

Lines changed: 28 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,13 @@
1010
#ifdef UNIX_ENABLED
1111
#include <dlfcn.h>
1212
#include <unistd.h>
13+
#include <cstdlib>
14+
#endif
15+
16+
#ifdef MACOS_ENABLED
17+
#include <cerrno>
18+
#include <libproc.h>
19+
#include "util/macos.h"
1320
#endif
1421

1522
#ifdef WINDOWS_ENABLED
@@ -29,8 +36,10 @@ namespace pygodot {
2936
void system_quick_exit(int status) {
3037
#ifdef WINDOWS_ENABLED
3138
TerminateProcess(GetCurrentProcess(), status);
32-
#else
39+
#elif defined(_GLIBCXX_HAVE_QUICK_EXIT)
3340
std::quick_exit(status);
41+
#else
42+
std::_Exit(status);
3443
#endif
3544
}
3645

@@ -58,11 +67,22 @@ std::filesystem::path get_executable_path() {
5867
memset(buffer, 0, sizeof(buffer));
5968
ssize_t len = readlink("/proc/self/exe", buffer, sizeof(buffer));
6069
return std::filesystem::path(buffer);
61-
#endif
62-
#ifdef WINDOWS_ENABLED
70+
#elif defined(MACOS_ENABLED)
71+
char pathbuf[PROC_PIDPATHINFO_MAXSIZE];
72+
73+
const pid_t pid = getpid();
74+
const int ret = proc_pidpath(pid, pathbuf, sizeof(pathbuf));
75+
if (ret <= 0) {
76+
fprintf(stderr, "PID %d: proc_pidpath ();\n", pid);
77+
fprintf(stderr, " %s\n", strerror(errno));
78+
}
79+
return std::filesystem::path(pathbuf);
80+
#elif defined(WINDOWS_ENABLED)
6381
WCHAR buffer[4096];
6482
GetModuleFileNameW(nullptr, buffer, 4096);
6583
return std::filesystem::path(buffer);
84+
#else
85+
#error System not supported.
6686
#endif
6787
}
6888

@@ -87,9 +107,9 @@ std::vector<std::string> get_argv() {
87107
}
88108

89109
fclose(cmdline_file);
90-
#endif
91-
92-
#ifdef WINDOWS_ENABLED
110+
#elif defined(MACOS_ENABLED)
111+
return macos::get_argv();
112+
#elif defined(WINDOWS_ENABLED)
93113
int num_args;
94114
LPWSTR* arg_list = CommandLineToArgvW(GetCommandLineW(), &num_args);
95115

@@ -105,6 +125,8 @@ std::vector<std::string> get_argv() {
105125
args.emplace_back(arg);
106126
}
107127
}
128+
#else
129+
#error System not supported.
108130
#endif
109131

110132
return args;

test/python.gdextension

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@ entry_symbol = "python_extension_init"
55

66
[libraries]
77

8-
macos = "res://bin/macos/libgodot-python.macos.framework"
8+
macos.x86_64 = "res://bin/macos-x86_64/libgodot-python.macos.x86_64.dylib"
9+
macos.arm64 = "res://bin/macos-arm64/libgodot-python.macos.arm64.dylib"
910

1011
windows.x86_32 = "res://bin/windows-x86_32/libgodot-python.windows.x86_32.dll"
1112
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"
1617

1718
android.x86_64 = "res://bin/android-x86_64/libgodot-python.android.x86_64.so"
1819
android.arm64 = "res://bin/android-arm64/libgodot-python.android.arm64.so"
19-

tools/build/build_utils.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -124,9 +124,9 @@ def process_arch(env):
124124
# No architecture specified. Default to arm64 if building for Android,
125125
# universal if building for macOS or iOS, wasm32 if building for web,
126126
# otherwise default to the host architecture.
127-
if env["platform"] in ["macos", "ios"]:
128-
env["arch"] = "universal"
129-
elif env["platform"] == "android":
127+
# if env["platform"] in ["macos", "ios"]:
128+
# env["arch"] = "universal"
129+
if env["platform"] == "android":
130130
env["arch"] = "arm64"
131131
elif env["platform"] == "web":
132132
env["arch"] = "wasm32"

tools/build/prepare_python.py

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,32 @@ def add_platform_config(*args, **kwargs):
5959
executable = 'python.exe',
6060
)
6161

62+
add_platform_config(
63+
platform = 'macos',
64+
arch = 'x86_64',
65+
source_url = 'https://github.com/indygreg/python-build-standalone/releases/download/'
66+
'20231002/cpython-3.12.0+20231002-x86_64-apple-darwin-install_only.tar.gz',
67+
so_suffixes = ['.so', '.dylib'],
68+
ext_suffixes = ['.so'],
69+
so_path = 'lib/libpython3.12.dylib',
70+
python_lib_dir = 'lib/python3.12',
71+
python_ext_dir = 'lib/python3.12/lib-dynload',
72+
executable = 'bin/python3.12',
73+
)
74+
75+
add_platform_config(
76+
platform = 'macos',
77+
arch = 'arm64',
78+
source_url = 'https://github.com/indygreg/python-build-standalone/releases/download/'
79+
'20231002/cpython-3.12.0+20231002-aarch64-apple-darwin-install_only.tar.gz',
80+
so_suffixes = ['.so', '.dylib'],
81+
ext_suffixes = ['.so'],
82+
so_path = 'lib/libpython3.12.dylib',
83+
python_lib_dir = 'lib/python3.12',
84+
python_ext_dir = 'lib/python3.12/lib-dynload',
85+
executable = 'bin/python3.12',
86+
)
87+
6288

6389
def fetch_python_for_platform(platform: str, arch: str, dest_dir: pathlib.Path):
6490
config = platform_configs[(platform, arch)]
@@ -80,11 +106,21 @@ def prepare_for_platform(platform: str, arch: str,
80106
shutil.unpack_archive(src_dir / pathlib.Path(config.source_url).name, extract_dir = src_dir)
81107

82108
src = src_dir / 'python'
109+
src_lib_path = src / config.so_path
110+
lib_filename = pathlib.Path(config.so_path).name
111+
112+
if platform == 'macos':
113+
# Rename the library id (which we depend on) to be in @rpath.
114+
# (it defaults to /install/lib/)
115+
subprocess.run(['install_name_tool', '-id', f'@rpath/{lib_filename}', src_lib_path], check=True)
83116

84117
dest_dir.mkdir(parents=True, exist_ok=True)
118+
shutil.copy2(src_lib_path, dest_dir)
85119

86-
shutil.copy2(src / config.so_path, dest_dir)
87-
subprocess.run(['strip', '-s', str(dest_dir / pathlib.Path(config.so_path).name)], check=True)
120+
if platform == 'macos':
121+
subprocess.run(['strip', '-x', dest_dir / lib_filename], check=True)
122+
else:
123+
subprocess.run(['strip', '-s', dest_dir / lib_filename], check=True)
88124

89125
if (src / config.python_ext_dir).exists():
90126
dest_ext_dir = dest_dir / 'python3.12' / 'lib-dynload'

tools/build/python_config.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ def normpath(path: str) -> str:
111111

112112
config_vars.link_libs = _stable_unique([
113113
*itertools.chain(*(
114-
(v.removeprefix('-l') for v in value.split())
114+
(v.removeprefix('-l') for v in value.split() if v.startswith('-l'))
115115
for name in ('LIBS', 'SYSLIBS')
116116
if (value := sysconfig_vars.get(name))
117117
)),

0 commit comments

Comments
 (0)