Skip to content

Commit c90d7c5

Browse files
committed
Support new GDExtensionInterfaceGetProcAddress based GDExtension interface
1 parent c03cc3a commit c90d7c5

File tree

15 files changed

+659
-58
lines changed

15 files changed

+659
-58
lines changed

.github/workflows/build.yml

+111-10
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,16 @@ on:
1010
- godot4-meson
1111

1212

13+
# Cancel run if another commit is pushed on the branch
14+
concurrency:
15+
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
16+
cancel-in-progress: true
17+
18+
1319
# Global Settings
1420
env:
1521
PYTHON_VERSION: "3.8" # Python to run the build, no the one shipped !
22+
BLEEDING_EDGE_GODOT: true
1623

1724

1825
jobs:
@@ -50,34 +57,79 @@ jobs:
5057
LD: lld
5158
PLATFORM: linux-x86_64
5259
steps:
60+
5361
- name: 'Checkout'
5462
uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # pin@v3.3.0
5563
with:
5664
submodules: true
65+
5766
- name: 'Set up Python'
5867
uses: actions/setup-python@d27e3f3d7c64b4bbf8e4abfb9b63b83e846e0435 # pin@v4.5.0
5968
with:
6069
python-version: ${{ env.PYTHON_VERSION }}
70+
6171
- name: 'Setup venv'
6272
run: |
6373
set -eux
6474
${{ env.CC }} --version
6575
python --version
6676
pip install -U pip
6777
pip install -r requirements.in
78+
79+
- name: Download bleeding edge Godot 🏗️
80+
uses: dsnopek/action-download-artifact@1322f74e2dac9feed2ee76a32d9ae1ca3b4cf4e9
81+
if: ${{ env.BLEEDING_EDGE_GODOT }}
82+
with:
83+
repo: godotengine/godot
84+
branch: master
85+
event: push
86+
workflow: linux_builds.yml
87+
workflow_conclusion: success
88+
name: linux-editor-mono
89+
search_artifacts: true
90+
check_artifacts: true
91+
ensure_latest: true
92+
path: godot-artifacts
93+
94+
- name: 'Setup bleeding edge Godot 🏗️'
95+
id: setup-godot
96+
if: ${{ env.BLEEDING_EDGE_GODOT }}
97+
run: |
98+
set -eux
99+
100+
GODOT_BIN=./godot-artifacts/godot.linuxbsd.editor.x86_64.mono
101+
GDEXTENSION_DIR=./gdextension_api
102+
ls godot-artifacts
103+
104+
chmod +x $GODOT_BIN
105+
$GODOT_BIN --headless --version
106+
107+
mkdir $GDEXTENSION_DIR && pushd $GDEXTENSION_DIR
108+
$GODOT_BIN --headless --dump-extension-api
109+
mkdir godot && pushd godot
110+
$GODOT_BIN --headless --dump-gdextension-interface
111+
popd && popd
112+
113+
echo "EXTRA_MESON_SETUP_ARGS='-D gdextension_path=$GDEXTENSION_DIR'" >> $GITHUB_OUTPUT
114+
echo "EXTRA_RUN_TESTS_ARGS='--godot-binary=$GODOT_BIN'" >> $GITHUB_OUTPUT
115+
68116
- name: 'Setup project'
69-
run: python .github/scripts/meson_setup_or_dump_log.py build/
117+
run: python .github/scripts/meson_setup_or_dump_log.py build/ ${{ steps.setup-godot.outputs.EXTRA_MESON_SETUP_ARGS }}
118+
70119
- name: 'Build project'
71120
run: meson compile -C build/
72121
- name: 'Run tests'
73122
run: |
74123
set -eux
75-
python tests/run.py 0-gdscript --build-dir build/ -- --headless
76-
python tests/run.py 1-gdextension --build-dir build/ -- --headless
77-
python tests/run.py 2-pythonscript-init --build-dir build/ -- --headless
78-
python tests/run.py 3-pythonscript-cython-only --build-dir build/ -- --headless
124+
ARGS=--build-dir=build/ ${{ steps.setup-godot.outputs.EXTRA_RUN_TESTS_ARGS }} -- --headless
125+
python tests/run.py 0-gdscript $ARGS
126+
python tests/run.py 1-gdextension $ARGS
127+
python tests/run.py 2-pythonscript-init $ARGS
128+
python tests/run.py 3-pythonscript-cython-only $ARGS
129+
79130
# - name: 'Generate artifact archive'
80131
# run: meson compile -C build/ release
132+
81133
# - name: 'Export release artifact'
82134
# uses: actions/upload-artifact@11830c9f4d30053679cb8904e3b3ce1b8c00bf40 # pin@v2
83135
# with:
@@ -94,35 +146,81 @@ jobs:
94146
env:
95147
PLATFORM: 'windows-x86_64'
96148
steps:
149+
97150
- name: 'Checkout'
98151
uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # pin@v3.3.0
99152
with:
100153
submodules: true
154+
101155
- name: 'Set up MSVC'
102156
uses: egor-tensin/vs-shell@9a932a62d05192eae18ca370155cf877eecc2202 # pin@v2.1
157+
103158
- name: 'Set up Python'
104159
uses: actions/setup-python@d27e3f3d7c64b4bbf8e4abfb9b63b83e846e0435 # pin@v4.5.0
105160
with:
106161
python-version: ${{ env.PYTHON_VERSION }}
162+
107163
- name: 'Setup venv'
108164
shell: bash
109165
run: |
110166
set -eux
111167
python --version
112168
pip install -U pip
113169
pip install -r requirements.in
170+
171+
- name: Download bleeding edge Godot 🏗️
172+
uses: dsnopek/action-download-artifact@1322f74e2dac9feed2ee76a32d9ae1ca3b4cf4e9
173+
if: ${{ env.BLEEDING_EDGE_GODOT }}
174+
with:
175+
repo: godotengine/godot
176+
branch: master
177+
event: push
178+
workflow: windows_builds.yml
179+
workflow_conclusion: success
180+
name: windows-editor
181+
search_artifacts: true
182+
check_artifacts: true
183+
ensure_latest: true
184+
path: godot-artifacts
185+
186+
- name: 'Setup bleeding edge Godot 🏗️'
187+
id: setup-godot
188+
if: ${{ env.BLEEDING_EDGE_GODOT }}
189+
run: |
190+
set -eux
191+
192+
GODOT_BIN=./godot-artifacts/godot.windows.editor.x86_64.mono
193+
GDEXTENSION_DIR=./gdextension_api
194+
ls godot-artifacts
195+
196+
chmod +x $GODOT_BIN
197+
$GODOT_BIN --headless --version
198+
199+
mkdir $GDEXTENSION_DIR && pushd $GDEXTENSION_DIR
200+
$GODOT_BIN --headless --dump-extension-api
201+
mkdir godot && pushd godot
202+
$GODOT_BIN --headless --dump-gdextension-interface
203+
popd && popd
204+
205+
echo "EXTRA_MESON_SETUP_ARGS='-D gdextension_path=$GDEXTENSION_DIR'" >> $GITHUB_OUTPUT
206+
echo "EXTRA_RUN_TESTS_ARGS='--godot-binary=$GODOT_BIN'" >> $GITHUB_OUTPUT
207+
114208
- name: 'Setup project'
115-
run: python .github/scripts/meson_setup_or_dump_log.py build/
209+
run: python .github/scripts/meson_setup_or_dump_log.py build/ ${{ steps.setup-godot.outputs.EXTRA_MESON_SETUP_ARGS }}
210+
116211
- name: 'Build project'
117212
run: meson compile -C build/
213+
118214
- name: 'Run tests'
119215
shell: bash
120216
run: |
121217
set -eux
122-
python tests/run.py 0-gdscript --build-dir build/ -- --headless
123-
python tests/run.py 1-gdextension --build-dir build/ -- --headless
124-
python tests/run.py 2-pythonscript-init --build-dir build/ -- --headless
125-
python tests/run.py 3-pythonscript-cython-only --build-dir build/ -- --headless
218+
ARGS=--build-dir=build/ ${{ steps.setup-godot.outputs.EXTRA_RUN_TESTS_ARGS }} -- --headless
219+
python tests/run.py 0-gdscript $ARGS
220+
python tests/run.py 1-gdextension $ARGS
221+
python tests/run.py 2-pythonscript-init $ARGS
222+
python tests/run.py 3-pythonscript-cython-only $ARGS
223+
126224
# - name: 'Install Mesa3D OpenGL'
127225
# shell: bash
128226
# run: |
@@ -140,10 +238,13 @@ jobs:
140238
# 7z.exe x mesa.7z
141239
# ls -lh opengl32.dll # Sanity check
142240
# popd
241+
143242
# - name: 'Run tests'
144243
# run: python tests/run.py --build-dir build/ --godot-binary ${{ GODOT_BINARY_VERSION }}
244+
145245
# - name: 'Generate artifact archive'
146246
# run: meson compile -C build/ release
247+
147248
# - name: 'Export release artifact'
148249
# uses: actions/upload-artifact@11830c9f4d30053679cb8904e3b3ce1b8c00bf40 # pin@v2
149250
# with:

meson.build

+7
Original file line numberDiff line numberDiff line change
@@ -246,6 +246,13 @@ endif
246246
if get_option('gdextension_path') != ''
247247
# GDExtension already exists
248248
gdextension_path = join_paths(meson.project_source_root(), get_option('gdextension_path'))
249+
run_command(
250+
python,
251+
'scripts/symlink.py',
252+
gdextension_path,
253+
join_paths(meson.project_build_root(), 'gdextension_api'),
254+
check: true,
255+
)
249256
else
250257
# Download Godot and generate it GDextension API
251258
message('Fetching Godot v@0@ binary and generating GDExtension...'.format(get_option('godot_version')))

misc/release_pythonscript.gdextension

+1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
[configuration]
22

33
entry_symbol = "pythonscript_init"
4+
compatibility_minimum = "4.1"
45

56
[libraries]
67

scripts/extension_api_parser/classes.py

+3
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ class ClassMethodSpec:
5858
is_virtual: bool
5959
is_property_accessor: bool
6060
hash: Optional[int]
61+
hash_compatibility: Optional[int]
6162
return_type: TypeInUse
6263
arguments: List[ClassMethodArgumentSpec]
6364

@@ -72,6 +73,7 @@ def parse(cls, item: dict) -> "ClassMethodSpec":
7273
item["return_type"] = TypeInUse.parse(return_value["type"])
7374
item.setdefault("arguments", [])
7475
item.setdefault("hash", None)
76+
item.setdefault("hash_compatibility", None)
7577
item.setdefault("is_property_accessor", False)
7678
assert_api_consistency(cls, item)
7779
return cls(
@@ -83,6 +85,7 @@ def parse(cls, item: dict) -> "ClassMethodSpec":
8385
is_virtual=item["is_virtual"],
8486
is_property_accessor=item["is_property_accessor"],
8587
hash=item["hash"],
88+
hash_compatibility=item["hash_compatibility"],
8689
return_type=item["return_type"],
8790
arguments=[ClassMethodArgumentSpec.parse(x) for x in item["arguments"]],
8891
)

scripts/symlink.py

+43
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
#! /usr/bin/env python3
2+
3+
import sys
4+
import os
5+
6+
7+
def symlink(src, dst):
8+
try:
9+
os.unlink(dst)
10+
except Exception:
11+
pass
12+
13+
if sys.platform == "win32":
14+
try:
15+
import _winapi
16+
17+
_winapi.CreateJunction(src, dst)
18+
except Exception as e:
19+
raise SystemExit(
20+
f"Can't do a NTFS junction as symlink fallback ({src} -> {dst})"
21+
) from e
22+
23+
else:
24+
try:
25+
os.symlink(src, dst)
26+
except Exception as e:
27+
raise SystemExit(f"Can't create symlink ({src} -> {dst})") from e
28+
29+
30+
USAGE = "usage: symlink.py SRC1 SRC2 ... DST1 DST2 ..."
31+
32+
33+
srcs_and_dsts = sys.argv[1:]
34+
srcs = srcs_and_dsts[: len(srcs_and_dsts) // 2]
35+
dsts = srcs_and_dsts[len(srcs_and_dsts) // 2 :]
36+
37+
38+
if len(srcs) != len(dsts):
39+
raise SystemExit(USAGE)
40+
41+
42+
for src, dst in zip(srcs, dsts):
43+
symlink(src, dst)

src/godot/classes_pyx/class.pyx.j2

+57-4
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ from cpython.object cimport PyObject_GenericGetAttr, PyObject_GenericSetAttr
55
{% endif %}
66

77
cdef class {{ cls.cy_type }}({{ cls.inherits.cy_type if cls.inherits else "" }}):
8-
{% if cls.inherits is none %}
8+
{% if cls.inherits is none %}
99

1010
{# @staticmethod
1111
cpdef inline {{ cls.cy_type}} new():
@@ -122,9 +122,7 @@ cdef class {{ cls.cy_type }}({{ cls.inherits.cy_type if cls.inherits else "" }})
122122

123123
def call(self, name, *args):
124124
return self.callv(name, GDArray(args))
125-
{% else %}
126-
pass
127-
{% endif %}
125+
{% endif %} # if cls.inherits is none
128126

129127
@staticmethod
130128
cdef {{ cls.cy_type }} from_ptr(gd_object_t ptr):
@@ -134,5 +132,60 @@ cdef class {{ cls.cy_type }}({{ cls.inherits.cy_type if cls.inherits else "" }})
134132
# Note if the object is a reference, we stole it from the caller given we
135133
# don't call `Reference.reference` here
136134
return wrapper
135+
{#
136+
{% if cls.is_instantiable %}
137+
138+
{% if cls.is_refcounted %}
139+
def __init__(self):
140+
if __{{ cls.name }}_constructor == NULL:
141+
raise NotImplementedError(__ERR_MSG_BINDING_NOT_AVAILABLE)
142+
cdef godot_bool __ret
143+
with nogil:
144+
self._gd_ptr = __{{ cls["name"] }}_constructor()
145+
146+
if self._gd_ptr is NULL:
147+
raise MemoryError
148+
149+
gdapi10.godot_method_bind_ptrcall(
150+
__methbind__Reference__init_ref,
151+
self._gd_ptr,
152+
NULL,
153+
&__ret
154+
)
155+
{% else %} # if cls.is_refcounted
156+
@staticmethod
157+
def new():
158+
if __{{ cls.name }}_constructor == NULL:
159+
raise NotImplementedError(__ERR_MSG_BINDING_NOT_AVAILABLE)
160+
# Call to __new__ bypasses __init__ constructor
161+
cdef {{ cls.name }} wrapper = {{ cls.name }}.__new__({{ cls.name }})
162+
with nogil:
163+
wrapper._gd_ptr = __{{ cls.name }}_constructor()
164+
if wrapper._gd_ptr is NULL:
165+
raise MemoryError
166+
return wrapper
167+
{% endif %} # if cls.is_refcounted
168+
169+
{% if cls.name == "RefCounted" %}
170+
@classmethod
171+
def new(cls):
172+
raise RuntimeError(f"Refcounted Godot object must be created with `{ cls.__name__ }()`")
173+
174+
def __dealloc__(self):
175+
cdef godot_bool __ret
176+
if self._gd_ptr == NULL:
177+
return
178+
with nogil:
179+
gdapi10.godot_method_bind_ptrcall(
180+
__methbind__Reference__unreference,
181+
self._gd_ptr,
182+
NULL,
183+
&__ret
184+
)
185+
if __ret:
186+
gdapi10.godot_object_destroy(self._gd_ptr)
187+
{% endif %}
188+
189+
{% endif %} # if cls.is_instantiable #}
137190

138191
{% endmacro %}

0 commit comments

Comments
 (0)