Skip to content

Commit

Permalink
Support new GDExtensionInterfaceGetProcAddress based GDExtension inte…
Browse files Browse the repository at this point in the history
…rface
  • Loading branch information
touilleMan committed Jun 4, 2023
1 parent c03cc3a commit c90d7c5
Show file tree
Hide file tree
Showing 15 changed files with 659 additions and 58 deletions.
121 changes: 111 additions & 10 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,16 @@ on:
- godot4-meson


# Cancel run if another commit is pushed on the branch
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true


# Global Settings
env:
PYTHON_VERSION: "3.8" # Python to run the build, no the one shipped !
BLEEDING_EDGE_GODOT: true


jobs:
Expand Down Expand Up @@ -50,34 +57,79 @@ jobs:
LD: lld
PLATFORM: linux-x86_64
steps:

- name: 'Checkout'
uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # [email protected]
with:
submodules: true

- name: 'Set up Python'
uses: actions/setup-python@d27e3f3d7c64b4bbf8e4abfb9b63b83e846e0435 # [email protected]
with:
python-version: ${{ env.PYTHON_VERSION }}

- name: 'Setup venv'
run: |
set -eux
${{ env.CC }} --version
python --version
pip install -U pip
pip install -r requirements.in
- name: Download bleeding edge Godot 🏗️
uses: dsnopek/action-download-artifact@1322f74e2dac9feed2ee76a32d9ae1ca3b4cf4e9
if: ${{ env.BLEEDING_EDGE_GODOT }}
with:
repo: godotengine/godot
branch: master
event: push
workflow: linux_builds.yml
workflow_conclusion: success
name: linux-editor-mono
search_artifacts: true
check_artifacts: true
ensure_latest: true
path: godot-artifacts

- name: 'Setup bleeding edge Godot 🏗️'
id: setup-godot
if: ${{ env.BLEEDING_EDGE_GODOT }}
run: |
set -eux
GODOT_BIN=./godot-artifacts/godot.linuxbsd.editor.x86_64.mono
GDEXTENSION_DIR=./gdextension_api
ls godot-artifacts
chmod +x $GODOT_BIN
$GODOT_BIN --headless --version
mkdir $GDEXTENSION_DIR && pushd $GDEXTENSION_DIR
$GODOT_BIN --headless --dump-extension-api
mkdir godot && pushd godot
$GODOT_BIN --headless --dump-gdextension-interface
popd && popd
echo "EXTRA_MESON_SETUP_ARGS='-D gdextension_path=$GDEXTENSION_DIR'" >> $GITHUB_OUTPUT
echo "EXTRA_RUN_TESTS_ARGS='--godot-binary=$GODOT_BIN'" >> $GITHUB_OUTPUT
- name: 'Setup project'
run: python .github/scripts/meson_setup_or_dump_log.py build/
run: python .github/scripts/meson_setup_or_dump_log.py build/ ${{ steps.setup-godot.outputs.EXTRA_MESON_SETUP_ARGS }}

- name: 'Build project'
run: meson compile -C build/
- name: 'Run tests'
run: |
set -eux
python tests/run.py 0-gdscript --build-dir build/ -- --headless
python tests/run.py 1-gdextension --build-dir build/ -- --headless
python tests/run.py 2-pythonscript-init --build-dir build/ -- --headless
python tests/run.py 3-pythonscript-cython-only --build-dir build/ -- --headless
ARGS=--build-dir=build/ ${{ steps.setup-godot.outputs.EXTRA_RUN_TESTS_ARGS }} -- --headless
python tests/run.py 0-gdscript $ARGS
python tests/run.py 1-gdextension $ARGS
python tests/run.py 2-pythonscript-init $ARGS
python tests/run.py 3-pythonscript-cython-only $ARGS
# - name: 'Generate artifact archive'
# run: meson compile -C build/ release

# - name: 'Export release artifact'
# uses: actions/upload-artifact@11830c9f4d30053679cb8904e3b3ce1b8c00bf40 # pin@v2
# with:
Expand All @@ -94,35 +146,81 @@ jobs:
env:
PLATFORM: 'windows-x86_64'
steps:

- name: 'Checkout'
uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # [email protected]
with:
submodules: true

- name: 'Set up MSVC'
uses: egor-tensin/vs-shell@9a932a62d05192eae18ca370155cf877eecc2202 # [email protected]

- name: 'Set up Python'
uses: actions/setup-python@d27e3f3d7c64b4bbf8e4abfb9b63b83e846e0435 # [email protected]
with:
python-version: ${{ env.PYTHON_VERSION }}

- name: 'Setup venv'
shell: bash
run: |
set -eux
python --version
pip install -U pip
pip install -r requirements.in
- name: Download bleeding edge Godot 🏗️
uses: dsnopek/action-download-artifact@1322f74e2dac9feed2ee76a32d9ae1ca3b4cf4e9
if: ${{ env.BLEEDING_EDGE_GODOT }}
with:
repo: godotengine/godot
branch: master
event: push
workflow: windows_builds.yml
workflow_conclusion: success
name: windows-editor
search_artifacts: true
check_artifacts: true
ensure_latest: true
path: godot-artifacts

- name: 'Setup bleeding edge Godot 🏗️'
id: setup-godot
if: ${{ env.BLEEDING_EDGE_GODOT }}
run: |
set -eux
GODOT_BIN=./godot-artifacts/godot.windows.editor.x86_64.mono
GDEXTENSION_DIR=./gdextension_api
ls godot-artifacts
chmod +x $GODOT_BIN
$GODOT_BIN --headless --version
mkdir $GDEXTENSION_DIR && pushd $GDEXTENSION_DIR
$GODOT_BIN --headless --dump-extension-api
mkdir godot && pushd godot
$GODOT_BIN --headless --dump-gdextension-interface
popd && popd
echo "EXTRA_MESON_SETUP_ARGS='-D gdextension_path=$GDEXTENSION_DIR'" >> $GITHUB_OUTPUT
echo "EXTRA_RUN_TESTS_ARGS='--godot-binary=$GODOT_BIN'" >> $GITHUB_OUTPUT
- name: 'Setup project'
run: python .github/scripts/meson_setup_or_dump_log.py build/
run: python .github/scripts/meson_setup_or_dump_log.py build/ ${{ steps.setup-godot.outputs.EXTRA_MESON_SETUP_ARGS }}

- name: 'Build project'
run: meson compile -C build/

- name: 'Run tests'
shell: bash
run: |
set -eux
python tests/run.py 0-gdscript --build-dir build/ -- --headless
python tests/run.py 1-gdextension --build-dir build/ -- --headless
python tests/run.py 2-pythonscript-init --build-dir build/ -- --headless
python tests/run.py 3-pythonscript-cython-only --build-dir build/ -- --headless
ARGS=--build-dir=build/ ${{ steps.setup-godot.outputs.EXTRA_RUN_TESTS_ARGS }} -- --headless
python tests/run.py 0-gdscript $ARGS
python tests/run.py 1-gdextension $ARGS
python tests/run.py 2-pythonscript-init $ARGS
python tests/run.py 3-pythonscript-cython-only $ARGS
# - name: 'Install Mesa3D OpenGL'
# shell: bash
# run: |
Expand All @@ -140,10 +238,13 @@ jobs:
# 7z.exe x mesa.7z
# ls -lh opengl32.dll # Sanity check
# popd

# - name: 'Run tests'
# run: python tests/run.py --build-dir build/ --godot-binary ${{ GODOT_BINARY_VERSION }}

# - name: 'Generate artifact archive'
# run: meson compile -C build/ release

# - name: 'Export release artifact'
# uses: actions/upload-artifact@11830c9f4d30053679cb8904e3b3ce1b8c00bf40 # pin@v2
# with:
Expand Down
7 changes: 7 additions & 0 deletions meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -246,6 +246,13 @@ endif
if get_option('gdextension_path') != ''
# GDExtension already exists
gdextension_path = join_paths(meson.project_source_root(), get_option('gdextension_path'))
run_command(
python,
'scripts/symlink.py',
gdextension_path,
join_paths(meson.project_build_root(), 'gdextension_api'),
check: true,
)
else
# Download Godot and generate it GDextension API
message('Fetching Godot v@0@ binary and generating GDExtension...'.format(get_option('godot_version')))
Expand Down
1 change: 1 addition & 0 deletions misc/release_pythonscript.gdextension
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
[configuration]

entry_symbol = "pythonscript_init"
compatibility_minimum = "4.1"

[libraries]

Expand Down
3 changes: 3 additions & 0 deletions scripts/extension_api_parser/classes.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ class ClassMethodSpec:
is_virtual: bool
is_property_accessor: bool
hash: Optional[int]
hash_compatibility: Optional[int]
return_type: TypeInUse
arguments: List[ClassMethodArgumentSpec]

Expand All @@ -72,6 +73,7 @@ def parse(cls, item: dict) -> "ClassMethodSpec":
item["return_type"] = TypeInUse.parse(return_value["type"])
item.setdefault("arguments", [])
item.setdefault("hash", None)
item.setdefault("hash_compatibility", None)
item.setdefault("is_property_accessor", False)
assert_api_consistency(cls, item)
return cls(
Expand All @@ -83,6 +85,7 @@ def parse(cls, item: dict) -> "ClassMethodSpec":
is_virtual=item["is_virtual"],
is_property_accessor=item["is_property_accessor"],
hash=item["hash"],
hash_compatibility=item["hash_compatibility"],
return_type=item["return_type"],
arguments=[ClassMethodArgumentSpec.parse(x) for x in item["arguments"]],
)
Expand Down
43 changes: 43 additions & 0 deletions scripts/symlink.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
#! /usr/bin/env python3

import sys
import os


def symlink(src, dst):
try:
os.unlink(dst)
except Exception:
pass

if sys.platform == "win32":
try:
import _winapi

_winapi.CreateJunction(src, dst)
except Exception as e:
raise SystemExit(
f"Can't do a NTFS junction as symlink fallback ({src} -> {dst})"
) from e

else:
try:
os.symlink(src, dst)
except Exception as e:
raise SystemExit(f"Can't create symlink ({src} -> {dst})") from e


USAGE = "usage: symlink.py SRC1 SRC2 ... DST1 DST2 ..."


srcs_and_dsts = sys.argv[1:]
srcs = srcs_and_dsts[: len(srcs_and_dsts) // 2]
dsts = srcs_and_dsts[len(srcs_and_dsts) // 2 :]


if len(srcs) != len(dsts):
raise SystemExit(USAGE)


for src, dst in zip(srcs, dsts):
symlink(src, dst)
61 changes: 57 additions & 4 deletions src/godot/classes_pyx/class.pyx.j2
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ from cpython.object cimport PyObject_GenericGetAttr, PyObject_GenericSetAttr
{% endif %}

cdef class {{ cls.cy_type }}({{ cls.inherits.cy_type if cls.inherits else "" }}):
{% if cls.inherits is none %}
{% if cls.inherits is none %}

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

def call(self, name, *args):
return self.callv(name, GDArray(args))
{% else %}
pass
{% endif %}
{% endif %} # if cls.inherits is none

@staticmethod
cdef {{ cls.cy_type }} from_ptr(gd_object_t ptr):
Expand All @@ -134,5 +132,60 @@ cdef class {{ cls.cy_type }}({{ cls.inherits.cy_type if cls.inherits else "" }})
# Note if the object is a reference, we stole it from the caller given we
# don't call `Reference.reference` here
return wrapper
{#
{% if cls.is_instantiable %}
{% if cls.is_refcounted %}
def __init__(self):
if __{{ cls.name }}_constructor == NULL:
raise NotImplementedError(__ERR_MSG_BINDING_NOT_AVAILABLE)
cdef godot_bool __ret
with nogil:
self._gd_ptr = __{{ cls["name"] }}_constructor()
if self._gd_ptr is NULL:
raise MemoryError
gdapi10.godot_method_bind_ptrcall(
__methbind__Reference__init_ref,
self._gd_ptr,
NULL,
&__ret
)
{% else %} # if cls.is_refcounted
@staticmethod
def new():
if __{{ cls.name }}_constructor == NULL:
raise NotImplementedError(__ERR_MSG_BINDING_NOT_AVAILABLE)
# Call to __new__ bypasses __init__ constructor
cdef {{ cls.name }} wrapper = {{ cls.name }}.__new__({{ cls.name }})
with nogil:
wrapper._gd_ptr = __{{ cls.name }}_constructor()
if wrapper._gd_ptr is NULL:
raise MemoryError
return wrapper
{% endif %} # if cls.is_refcounted
{% if cls.name == "RefCounted" %}
@classmethod
def new(cls):
raise RuntimeError(f"Refcounted Godot object must be created with `{ cls.__name__ }()`")
def __dealloc__(self):
cdef godot_bool __ret
if self._gd_ptr == NULL:
return
with nogil:
gdapi10.godot_method_bind_ptrcall(
__methbind__Reference__unreference,
self._gd_ptr,
NULL,
&__ret
)
if __ret:
gdapi10.godot_object_destroy(self._gd_ptr)
{% endif %}
{% endif %} # if cls.is_instantiable #}

{% endmacro %}
Loading

0 comments on commit c90d7c5

Please sign in to comment.