From 7dfeeb2465fe3c990a0d3caa9e572824570f65f2 Mon Sep 17 00:00:00 2001 From: Lukas Tenbrink Date: Sun, 16 Feb 2025 17:47:57 +0100 Subject: [PATCH] Add bin folder to python targets, and adapt the regular python layout (save for the zip). Use the zip upload archive method in the github runner for macOS, too, to preserve permissions. --- .github/workflows/build.yml | 23 +++++++++--------- src/extension/extension.cpp | 11 +++++---- tools/build/prepare_python.py | 44 ++++++++++++++++++++++++++--------- 3 files changed, 51 insertions(+), 27 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index b07cf1c..d42b46c 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -64,12 +64,21 @@ jobs: run: | scons platform=${{ matrix.platform }} arch=${{ matrix.arch }} single_source=true - - name: Upload artifacts (Linux) - if: matrix.os == 'ubuntu-latest' + # Creating an archive explicitly preserves permissions in the artifact, + # which upload-artifact doesn't. See https://github.com/actions/upload-artifact/issues/38 + - name: Create archive (Linux, macOS) + if: matrix.os == 'ubuntu-latest' || matrix.os == 'macos-latest' + run: | + cd bin/ + zip -q -r ../godot-python-${{ matrix.platform }}-${{ matrix.arch }}.zip * + cd ../ + + - name: Upload artifacts (Linux, macOS) + if: matrix.os == 'ubuntu-latest' || matrix.os == 'macos-latest' uses: actions/upload-artifact@v4 with: name: godot-python-${{ matrix.platform }}-${{ matrix.arch }} - path: bin/**/* + path: godot-python-${{ matrix.platform }}-${{ matrix.arch }}.zip retention-days: 30 - name: Upload artifacts (Windows) @@ -83,14 +92,6 @@ jobs: !bin/**/*.exp retention-days: 30 - - name: Upload artifacts (macOS) - if: matrix.os == 'macos-latest' - uses: actions/upload-artifact@v4 - with: - name: godot-python-${{ matrix.platform }}-${{ matrix.arch }} - path: bin/**/* - retention-days: 30 - - name: Release artifact if: ${{ inputs.release }} run: | diff --git a/src/extension/extension.cpp b/src/extension/extension.cpp index 3b0b24a..76b4e0b 100644 --- a/src/extension/extension.cpp +++ b/src/extension/extension.cpp @@ -93,11 +93,11 @@ static struct runtime_config_t { // `python_home_path` if(likely_running_from_editor) { - python_home_path = lib_dir_path; + python_home_path = lib_dir_path / "python"; } else { auto platform_arch = std::string(PYGODOT_PLATFORM) + "-" + std::string(PYGODOT_ARCH); - python_home_path = lib_dir_path / "lib" / platform_arch; + python_home_path = lib_dir_path / "lib" / platform_arch / "python"; } } } runtime_config; @@ -171,12 +171,13 @@ static bool init_python_isolated() { auto py_version = py_major + "." + py_minor; auto py_version_no_dot = py_major + py_minor; auto python_zip_name = "python" + py_version_no_dot + ".zip"; + auto python_lib_path = runtime_config.python_home_path / "lib"; auto python_lib_name = "python" + py_version; add_module_search_path((runtime_config.python_home_path / python_zip_name).string()); - add_module_search_path((runtime_config.python_home_path / python_lib_name).string()); - add_module_search_path((runtime_config.python_home_path / python_lib_name / "lib-dynload").string()); - add_module_search_path((runtime_config.python_home_path / python_lib_name / "site-packages").string()); + add_module_search_path((python_lib_path / python_lib_name).string()); + add_module_search_path((python_lib_path / python_lib_name / "lib-dynload").string()); + add_module_search_path((python_lib_path / python_lib_name / "site-packages").string()); config.module_search_paths_set = 1; diff --git a/tools/build/prepare_python.py b/tools/build/prepare_python.py index 651f36f..9ced12d 100644 --- a/tools/build/prepare_python.py +++ b/tools/build/prepare_python.py @@ -17,6 +17,7 @@ class PlatformConfig: ext_suffixes: list[str] so_path: str python_lib_dir: str + python_bin_paths: list[str] python_ext_dir: str executable: str @@ -42,6 +43,7 @@ def add_platform_config(*args, **kwargs): ext_suffixes = ['.so'], so_path = 'lib/libpython3.12.so.1.0', python_lib_dir = 'lib/python3.12', + python_bin_paths = ['bin'], python_ext_dir = 'lib/python3.12/lib-dynload', executable = 'bin/python3.12', ) @@ -55,6 +57,7 @@ def add_platform_config(*args, **kwargs): ext_suffixes = ['.dll', '.pyd'], so_path = 'python312.dll', python_lib_dir = 'Lib', + python_bin_paths = ['python.exe', 'pythonw.exe'], python_ext_dir = 'DLLs', executable = 'python.exe', ) @@ -68,6 +71,7 @@ def add_platform_config(*args, **kwargs): ext_suffixes = ['.so'], so_path = 'lib/libpython3.12.dylib', python_lib_dir = 'lib/python3.12', + python_bin_paths = ['bin'], python_ext_dir = 'lib/python3.12/lib-dynload', executable = 'bin/python3.12', ) @@ -81,6 +85,7 @@ def add_platform_config(*args, **kwargs): ext_suffixes = ['.so'], so_path = 'lib/libpython3.12.dylib', python_lib_dir = 'lib/python3.12', + python_bin_paths = ['bin'], python_ext_dir = 'lib/python3.12/lib-dynload', executable = 'bin/python3.12', ) @@ -105,32 +110,49 @@ 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 + src_python = src_dir / 'python' + src_lib_path = src_python / 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) + subprocess.run(['install_name_tool', '-id', f'@rpath/python/lib/{lib_filename}', src_lib_path], check=True) + - dest_dir.mkdir(parents=True, exist_ok=True) - shutil.copy2(src_lib_path, dest_dir) + dest_dir_python = dest_dir / 'python' + dest_dir_python_lib = dest_dir_python / 'lib' + dest_dir_python_lib.mkdir(parents=True, exist_ok=True) + shutil.copy2(src_lib_path, dest_dir_python_lib) if platform == 'macos': - subprocess.run(['strip', '-x', dest_dir / lib_filename], check=True) + subprocess.run(['strip', '-x', dest_dir_python_lib / lib_filename], check=True) else: - subprocess.run(['strip', '-s', dest_dir / lib_filename], check=True) + subprocess.run(['strip', '-s', dest_dir_python_lib / lib_filename], check=True) + + for bin_path in config.python_bin_paths: + src_path: pathlib.Path = src_python / bin_path + + if src_path.is_file(): + shutil.copy2(src_path, dest_dir_python / bin_path) + elif src_path.is_dir(): + shutil.copytree(src_path, dest_dir_python / bin_path, dirs_exist_ok=True) + else: + raise RuntimeError(f"Cannot find file: {src_path}") + + if bin_path == 'bin': + # Ignore the bin path in Godot. + (dest_dir_python / bin_path / '.gdignore').touch() - if (src / config.python_ext_dir).exists(): - dest_ext_dir = dest_dir / 'python3.12' / 'lib-dynload' + if (src_python / config.python_ext_dir).exists(): + dest_ext_dir = dest_dir_python_lib / 'python3.12' / 'lib-dynload' dest_ext_dir.mkdir(parents=True, exist_ok=True) - for path in (src / config.python_ext_dir).iterdir(): + for path in (src_python / config.python_ext_dir).iterdir(): if any(suffix in path.suffixes for suffix in config.ext_suffixes): shutil.copy2(path, dest_ext_dir) - shutil.make_archive(dest_dir / 'python312', 'zip', root_dir=src / config.python_lib_dir, base_dir='') + shutil.make_archive(dest_dir / 'python312', 'zip', root_dir=src_python / config.python_lib_dir, base_dir='') def get_python_for_platform(platform: str, arch: str, src_dir: pathlib.Path) -> pathlib.Path: