From 79a60688cea128ba9cc4a35076f9f65ca57c0f92 Mon Sep 17 00:00:00 2001 From: rdb Date: Wed, 11 Oct 2023 15:53:57 +0200 Subject: [PATCH 1/9] pnmimage: Support reading of .bmp files with RLE8 compression --- panda/src/pnmimagetypes/bmp.h | 14 +-- panda/src/pnmimagetypes/pnmFileTypeBMP.h | 1 + .../pnmimagetypes/pnmFileTypeBMPReader.cxx | 114 ++++++++++++------ 3 files changed, 82 insertions(+), 47 deletions(-) diff --git a/panda/src/pnmimagetypes/bmp.h b/panda/src/pnmimagetypes/bmp.h index d825bc71a1..2c5a08006f 100644 --- a/panda/src/pnmimagetypes/bmp.h +++ b/panda/src/pnmimagetypes/bmp.h @@ -92,19 +92,7 @@ BMPlenrgbtable(int classv, unsigned long bitcount) pm_error(er_internal, "BMPlenrgbtable"); return 0; } - switch (classv) - { - case C_WIN: - lenrgb = 4; - break; - case C_OS2: - lenrgb = 3; - break; - default: - pm_error(er_internal, "BMPlenrgbtable"); - return 0; - } - + lenrgb = (classv == C_OS2) ? 3 : 4; return (1 << bitcount) * lenrgb; } diff --git a/panda/src/pnmimagetypes/pnmFileTypeBMP.h b/panda/src/pnmimagetypes/pnmFileTypeBMP.h index 6cd3e8e32f..57fb6aacc4 100644 --- a/panda/src/pnmimagetypes/pnmFileTypeBMP.h +++ b/panda/src/pnmimagetypes/pnmFileTypeBMP.h @@ -55,6 +55,7 @@ public: unsigned long offBits; unsigned short cBitCount; + unsigned short cCompression; int indexed; int classv; diff --git a/panda/src/pnmimagetypes/pnmFileTypeBMPReader.cxx b/panda/src/pnmimagetypes/pnmFileTypeBMPReader.cxx index 7b2fc6e18c..638650bd2c 100644 --- a/panda/src/pnmimagetypes/pnmFileTypeBMPReader.cxx +++ b/panda/src/pnmimagetypes/pnmFileTypeBMPReader.cxx @@ -177,6 +177,7 @@ BMPreadinfoheader( unsigned long *pcx, unsigned long *pcy, unsigned short *pcBitCount, + unsigned short *pcCompression, int *pclassv) { unsigned long cbFix; @@ -185,6 +186,7 @@ BMPreadinfoheader( unsigned long cx = 0; unsigned long cy = 0; unsigned short cBitCount = 0; + unsigned long cCompression = 0; int classv = 0; cbFix = GetLong(fp); @@ -229,7 +231,9 @@ BMPreadinfoheader( * for the required total. */ if (classv != C_OS2) { - for (int i = 0; i < (int)cbFix - 16; i += 4) { + cCompression = GetLong(fp); + + for (int i = 0; i < (int)cbFix - 20; i += 4) { GetLong(fp); } } @@ -273,11 +277,13 @@ BMPreadinfoheader( pm_message("cy: %d", cy); pm_message("cPlanes: %d", cPlanes); pm_message("cBitCount: %d", cBitCount); + pm_message("cCompression: %d", cCompression); #endif *pcx = cx; *pcy = cy; *pcBitCount = cBitCount; + *pcCompression = cCompression; *pclassv = classv; *ppos += cbFix; @@ -401,45 +407,84 @@ BMPreadbits(xel *array, xelval *alpha_array, unsigned long cx, unsigned long cy, unsigned short cBitCount, - int /* classv */, + unsigned long cCompression, int indexed, pixval *R, pixval *G, pixval *B) { - long y; + long y; - readto(fp, ppos, offBits); + readto(fp, ppos, offBits); - if(cBitCount > 24 && cBitCount != 32) - { - pm_error("%s: cannot handle cBitCount: %d" - ,ifname - ,cBitCount); + if (cBitCount > 24 && cBitCount != 32) { + pm_error("%s: cannot handle cBitCount: %d", ifname, cBitCount); + } + + if (cCompression == 1) { + // RLE8 compression + xel *row = array + (cy - 1) * cx; + xel *p = row; + unsigned long nbyte = 0; + while (true) { + int first = GetByte(fp); + int second = GetByte(fp); + nbyte += 2; + + if (first != 0) { + // Repeated index. + for (int i = 0; i < first; ++i) { + PPM_ASSIGN(*p, R[second], G[second], B[second]); + ++p; } - - /* - * The picture is stored bottom line first, top line last - */ - - for (y = (long)cy - 1; y >= 0; y--) - { - int rc; - rc = BMPreadrow(fp, ppos, array + y*cx, alpha_array + y*cx, cx, cBitCount, indexed, R, G, B); - if(rc == -1) - { - pm_error("%s: couldn't read row %d" - ,ifname - ,y); - } - if(rc%4) - { - pm_error("%s: row had bad number of bytes: %d" - ,ifname - ,rc); - } + } + else if (second == 0) { + // End of line. + row -= cx; + p = row; + } + else if (second == 1) { + // End of image. + break; + } + else if (second == 2) { + // Delta. + int xoffset = GetByte(fp); + int yoffset = GetByte(fp); + nbyte += 2; + row -= cx * yoffset; + p += xoffset - cx * yoffset; + } + else { + // Absolute run. + for (int i = 0; i < second; ++i) { + int v = GetByte(fp); + ++nbyte; + PPM_ASSIGN(*p, R[v], G[v], B[v]); + ++p; } - + nbyte += second; + if (second % 2) { + // Pad to 16-bit boundary. + GetByte(fp); + ++nbyte; + } + } + } + *ppos += nbyte; + } + else { + // The picture is stored bottom line first, top line last + for (y = (long)cy - 1; y >= 0; y--) { + int rc = BMPreadrow(fp, ppos, array + y*cx, alpha_array + y*cx, cx, cBitCount, indexed, R, G, B); + if (rc == -1) { + pm_error("%s: couldn't read row %d", ifname, y); + } + if (rc % 4) { + pm_error("%s: row had bad number of bytes: %d", ifname, rc); + } + } + } } /** @@ -474,7 +519,7 @@ Reader(PNMFileType *type, istream *file, bool owns_file, string magic_number) : pos = 0; BMPreadfileheader(file, &pos, &offBits); - BMPreadinfoheader(file, &pos, &cx, &cy, &cBitCount, &classv); + BMPreadinfoheader(file, &pos, &cx, &cy, &cBitCount, &cCompression, &classv); if (offBits != BMPoffbits(classv, cBitCount)) { pnmimage_bmp_cat.warning() @@ -523,9 +568,10 @@ Reader(PNMFileType *type, istream *file, bool owns_file, string magic_number) : int PNMFileTypeBMP::Reader:: read_data(xel *array, xelval *alpha_array) { BMPreadbits(array, alpha_array, _file, &pos, offBits, _x_size, _y_size, - cBitCount, classv, indexed, R, G, B); + cBitCount, cCompression, indexed, R, G, B); - if (pos != BMPlenfile(classv, cBitCount, _x_size, _y_size)) { + if (cCompression != 1 && + pos != BMPlenfile(classv, cBitCount, _x_size, _y_size)) { pnmimage_bmp_cat.warning() << "Read " << pos << " bytes, expected to read " << BMPlenfile(classv, cBitCount, _x_size, _y_size) << " bytes\n"; From ad8882123b67ea0a729ab956000603963a1d83ae Mon Sep 17 00:00:00 2001 From: David Crompton Date: Sun, 15 Jan 2023 13:35:19 +0000 Subject: [PATCH 2/9] Panda3DToolsGUI: Update setup.py to use setuptools and change Print statements to Python3 syntax --- contrib/src/panda3dtoolsgui/Panda3DToolsGUI.py | 6 +++--- contrib/src/panda3dtoolsgui/setup.py | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/contrib/src/panda3dtoolsgui/Panda3DToolsGUI.py b/contrib/src/panda3dtoolsgui/Panda3DToolsGUI.py index 331081016b..abd609a123 100644 --- a/contrib/src/panda3dtoolsgui/Panda3DToolsGUI.py +++ b/contrib/src/panda3dtoolsgui/Panda3DToolsGUI.py @@ -2650,11 +2650,11 @@ class main(wx.Frame): for inputFile in inputs: if (inputFile != ''): inputFilename = inputFile.split('\\')[-1] - print "Compare: ", inFile, filename, inputFile, inputFilename + print("Compare: ", inFile, filename, inputFile, inputFilename) if inputFilename == filename: inputTime = os.path.getmtime(inputFile) outputTime = os.path.getmtime(inFile) - print "Matched: ", (inputTime > outputTime) + print("Matched: ", (inputTime > outputTime)) inputChanged = (inputTime > outputTime) break ''' @@ -2848,7 +2848,7 @@ class main(wx.Frame): except ValueError: return - #print self.batchList + #print(self.batchList) def OnBatchItemEdit(self, event): selectedItemId = self.batchTree.GetSelections() diff --git a/contrib/src/panda3dtoolsgui/setup.py b/contrib/src/panda3dtoolsgui/setup.py index 8691c87923..d79dbc244e 100644 --- a/contrib/src/panda3dtoolsgui/setup.py +++ b/contrib/src/panda3dtoolsgui/setup.py @@ -1,4 +1,4 @@ -from distutils.core import setup +from setuptools import setup import py2exe setup(console=['Panda3DToolsGUI.py']) From 521cad206d980026509c671bc6ad42058b92c06b Mon Sep 17 00:00:00 2001 From: Mitchell Stokes Date: Wed, 11 Oct 2023 19:39:01 -0700 Subject: [PATCH 3/9] makepanda: Stop using deprecated distutils (#1549) Just duplicating locations.py from direct. It's a bit ugly, but makepanda is getting phased out anyways. Co-authored-by: rdb --- makepanda/installpanda.py | 2 +- makepanda/locations.py | 32 ++++++++++++++++++++++++++++++++ makepanda/makepackage.py | 2 +- makepanda/makepanda.py | 3 ++- makepanda/makepandacore.py | 27 +++++++++++++-------------- makepanda/makewheel.py | 4 ++-- 6 files changed, 51 insertions(+), 19 deletions(-) create mode 100644 makepanda/locations.py diff --git a/makepanda/installpanda.py b/makepanda/installpanda.py index d0111e0049..89e32c765e 100644 --- a/makepanda/installpanda.py +++ b/makepanda/installpanda.py @@ -9,9 +9,9 @@ ######################################################################## import os, sys, platform -from distutils.sysconfig import get_python_lib from optparse import OptionParser from makepandacore import * +from locations import get_python_lib MIME_INFO = ( diff --git a/makepanda/locations.py b/makepanda/locations.py new file mode 100644 index 0000000000..d410b77aff --- /dev/null +++ b/makepanda/locations.py @@ -0,0 +1,32 @@ +__all__ = [ + 'get_python_inc', + 'get_config_var', + 'get_python_version', + 'PREFIX', + 'get_python_lib', + 'get_config_vars', +] + +import sys + +if sys.version_info < (3, 12): + from distutils.sysconfig import * +else: + from sysconfig import * + + PREFIX = get_config_var('prefix') + + def get_python_inc(plat_specific=False): + path_name = 'platinclude' if plat_specific else 'include' + return get_path(path_name) + + def get_python_lib(plat_specific=False, standard_lib=False): + if standard_lib: + path_name = 'stdlib' + if plat_specific: + path_name = 'plat' + path_name + elif plat_specific: + path_name = 'platlib' + else: + path_name = 'purelib' + return get_path(path_name) diff --git a/makepanda/makepackage.py b/makepanda/makepackage.py index f57fc873e3..68c74523dc 100755 --- a/makepanda/makepackage.py +++ b/makepanda/makepackage.py @@ -1032,7 +1032,7 @@ def MakeInstallerAndroid(version, **kwargs): shutil.copy(os.path.join(source_dir, base), target) # Copy the Python standard library to the .apk as well. - from distutils.sysconfig import get_python_lib + from locations import get_python_lib stdlib_source = get_python_lib(False, True) stdlib_target = os.path.join("apkroot", "lib", "python{0}.{1}".format(*sys.version_info)) copy_python_tree(stdlib_source, stdlib_target) diff --git a/makepanda/makepanda.py b/makepanda/makepanda.py index 407c95bb33..d88a1cb153 100755 --- a/makepanda/makepanda.py +++ b/makepanda/makepanda.py @@ -23,11 +23,12 @@ except: exit(1) from makepandacore import * -from distutils.util import get_platform import time import os import sys +from sysconfig import get_platform + ######################################################################## ## ## PARSING THE COMMAND LINE OPTIONS diff --git a/makepanda/makepandacore.py b/makepanda/makepandacore.py index ad75ae6f1d..111a3a6717 100644 --- a/makepanda/makepandacore.py +++ b/makepanda/makepandacore.py @@ -7,7 +7,7 @@ import sys,os,time,stat,string,re,getopt,fnmatch,threading,signal,shutil,platform,glob,getpass,signal import subprocess -from distutils import sysconfig +import locations if sys.version_info >= (3, 0): import pickle @@ -2250,7 +2250,7 @@ def SdkLocatePython(prefer_thirdparty_python=False): # On macOS, search for the Python framework directory matching the # version number of our current Python version. sysroot = SDK.get("MACOSX", "") - version = sysconfig.get_python_version() + version = locations.get_python_version() py_fwx = "{0}/System/Library/Frameworks/Python.framework/Versions/{1}".format(sysroot, version) @@ -2275,19 +2275,19 @@ def SdkLocatePython(prefer_thirdparty_python=False): LibDirectory("PYTHON", py_fwx + "/lib") #elif GetTarget() == 'windows': - # SDK["PYTHON"] = os.path.dirname(sysconfig.get_python_inc()) - # SDK["PYTHONVERSION"] = "python" + sysconfig.get_python_version() + # SDK["PYTHON"] = os.path.dirname(locations.get_python_inc()) + # SDK["PYTHONVERSION"] = "python" + locations.get_python_version() # SDK["PYTHONEXEC"] = sys.executable else: - SDK["PYTHON"] = sysconfig.get_python_inc() - SDK["PYTHONVERSION"] = "python" + sysconfig.get_python_version() + abiflags + SDK["PYTHON"] = locations.get_python_inc() + SDK["PYTHONVERSION"] = "python" + locations.get_python_version() + abiflags SDK["PYTHONEXEC"] = os.path.realpath(sys.executable) if CrossCompiling(): # We need a version of Python we can run. SDK["PYTHONEXEC"] = sys.executable - host_version = "python" + sysconfig.get_python_version() + abiflags + host_version = "python" + locations.get_python_version() + abiflags if SDK["PYTHONVERSION"] != host_version: exit("Host Python version (%s) must be the same as target Python version (%s)!" % (host_version, SDK["PYTHONVERSION"])) @@ -3514,7 +3514,7 @@ def GetExtensionSuffix(): return '.so' def GetPythonABI(): - soabi = sysconfig.get_config_var('SOABI') + soabi = locations.get_config_var('SOABI') if soabi: return soabi @@ -3523,16 +3523,16 @@ def GetPythonABI(): if sys.version_info >= (3, 8): return soabi - debug_flag = sysconfig.get_config_var('Py_DEBUG') + debug_flag = locations.get_config_var('Py_DEBUG') if (debug_flag is None and hasattr(sys, 'gettotalrefcount')) or debug_flag: soabi += 'd' - malloc_flag = sysconfig.get_config_var('WITH_PYMALLOC') + malloc_flag = locations.get_config_var('WITH_PYMALLOC') if malloc_flag is None or malloc_flag: soabi += 'm' if sys.version_info < (3, 3): - usize = sysconfig.get_config_var('Py_UNICODE_SIZE') + usize = locations.get_config_var('Py_UNICODE_SIZE') if (usize is None and sys.maxunicode == 0x10ffff) or usize == 4: soabi += 'u' @@ -3648,14 +3648,13 @@ def GetCurrentPythonVersionInfo(): if PkgSkip("PYTHON"): return - from distutils.sysconfig import get_python_lib return { "version": SDK["PYTHONVERSION"][6:].rstrip('dmu'), "soabi": GetPythonABI(), "ext_suffix": GetExtensionSuffix(), "executable": sys.executable, - "purelib": get_python_lib(False), - "platlib": get_python_lib(True), + "purelib": locations.get_python_lib(False), + "platlib": locations.get_python_lib(True), } diff --git a/makepanda/makewheel.py b/makepanda/makewheel.py index 2f18789e78..55a48f62cf 100644 --- a/makepanda/makewheel.py +++ b/makepanda/makewheel.py @@ -2,7 +2,6 @@ Generates a wheel (.whl) file from the output of makepanda. """ from __future__ import print_function, unicode_literals -from distutils.util import get_platform import json import sys @@ -13,10 +12,11 @@ import zipfile import hashlib import tempfile import subprocess -from distutils.sysconfig import get_config_var from optparse import OptionParser from makepandacore import ColorText, LocateBinary, GetExtensionSuffix, SetVerbose, GetVerbose, GetMetadataValue from base64 import urlsafe_b64encode +from locations import get_config_var +from sysconfig import get_platform def get_abi_tag(): From 893f5ce4921d377e3ff9a7bef48846cf24a1c4d5 Mon Sep 17 00:00:00 2001 From: Mitchell Stokes Date: Thu, 12 Oct 2023 16:42:17 -0700 Subject: [PATCH 4/9] Fix assert on Py_SIZE(long) when using Python 3.12 Starting with Python 3.12, passing a PyLong into Py_SIZE() triggers an assertion. PyLong (and the whole C API) is transitioning to be more opaque and expose fewer implementation details. --- dtool/src/interrogatedb/py_compat.h | 12 ++++++++++++ panda/src/putil/bitArray_ext.cxx | 4 ++-- panda/src/putil/doubleBitMask_ext.I | 3 +-- tests/putil/test_bitarray.py | 3 +++ 4 files changed, 18 insertions(+), 4 deletions(-) diff --git a/dtool/src/interrogatedb/py_compat.h b/dtool/src/interrogatedb/py_compat.h index c5474b21ee..20946e7f48 100644 --- a/dtool/src/interrogatedb/py_compat.h +++ b/dtool/src/interrogatedb/py_compat.h @@ -243,6 +243,18 @@ INLINE PyObject *PyObject_CallMethodOneArg(PyObject *obj, PyObject *name, PyObje } #endif +/* Python 3.12 */ + +#if PY_VERSION_HEX < 0x030C0000 +# define PyLong_IsNonNegative(value) (Py_SIZE((value)) >= 0) +#else +INLINE bool PyLong_IsNonNegative(PyObject *value) { + int overflow = 0; + long longval = PyLong_AsLongAndOverflow(value, &overflow); + return overflow == 1 || longval >= 0; +} +#endif + /* Other Python implementations */ // _PyErr_OCCURRED is an undocumented macro version of PyErr_Occurred. diff --git a/panda/src/putil/bitArray_ext.cxx b/panda/src/putil/bitArray_ext.cxx index 215d920cb4..6e0d860238 100644 --- a/panda/src/putil/bitArray_ext.cxx +++ b/panda/src/putil/bitArray_ext.cxx @@ -32,7 +32,7 @@ __init__(PyObject *init_value) { } #endif - if (!PyLong_Check(init_value) || Py_SIZE(init_value) < 0) { + if (!PyLong_Check(init_value) || !PyLong_IsNonNegative(init_value)) { PyErr_SetString(PyExc_ValueError, "BitArray constructor requires a positive integer"); return; } @@ -88,7 +88,7 @@ __getstate__() const { */ void Extension:: __setstate__(PyObject *state) { - if (Py_SIZE(state) >= 0) { + if (PyLong_IsNonNegative(state)) { __init__(state); } else { PyObject *inverted = PyNumber_Invert(state); diff --git a/panda/src/putil/doubleBitMask_ext.I b/panda/src/putil/doubleBitMask_ext.I index b774f22096..748def0371 100644 --- a/panda/src/putil/doubleBitMask_ext.I +++ b/panda/src/putil/doubleBitMask_ext.I @@ -28,8 +28,7 @@ __init__(PyObject *init_value) { return; } #endif - - if (!PyLong_Check(init_value) || Py_SIZE(init_value) < 0) { + if (!PyLong_Check(init_value) || !PyLong_IsNonNegative(init_value)) { PyErr_SetString(PyExc_ValueError, "DoubleBitMask constructor requires a positive integer"); return; } diff --git a/tests/putil/test_bitarray.py b/tests/putil/test_bitarray.py index 5da2fdc725..293be8df74 100644 --- a/tests/putil/test_bitarray.py +++ b/tests/putil/test_bitarray.py @@ -32,6 +32,9 @@ def test_bitarray_pickle(): ba = BitArray(123) assert ba == pickle.loads(pickle.dumps(ba, -1)) + ba = BitArray(1 << 128) + assert ba == pickle.loads(pickle.dumps(ba, -1)) + def test_bitarray_has_any_of(): ba = BitArray() From 225b577ccd9c0725158824c3ff697a5b3d798f40 Mon Sep 17 00:00:00 2001 From: rdb Date: Fri, 13 Oct 2023 10:55:17 +0200 Subject: [PATCH 5/9] tests: Skip Cg tests on arm64 machines --- tests/display/test_cg_shader.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/display/test_cg_shader.py b/tests/display/test_cg_shader.py index 8a6c4d7a74..2597b39971 100644 --- a/tests/display/test_cg_shader.py +++ b/tests/display/test_cg_shader.py @@ -1,4 +1,6 @@ import os +import platform +import pytest from panda3d import core @@ -16,12 +18,14 @@ def run_cg_compile_check(gsg, shader_path, expect_fail=False): assert shader is not None +@pytest.mark.skipif(platform.machine().lower() == 'arm64', reason="Cg not supported on arm64") def test_cg_compile_error(gsg): """Test getting compile errors from bad Cg shaders""" shader_path = core.Filename(SHADERS_DIR, 'cg_bad.sha') run_cg_compile_check(gsg, shader_path, expect_fail=True) +@pytest.mark.skipif(platform.machine().lower() == 'arm64', reason="Cg not supported on arm64") def test_cg_from_file(gsg): """Test compiling Cg shaders from files""" shader_path = core.Filename(SHADERS_DIR, 'cg_simple.sha') From 7f0eafcc27940e753c1ff8dd5facf2322fba5f72 Mon Sep 17 00:00:00 2001 From: rdb Date: Fri, 13 Oct 2023 12:10:08 +0200 Subject: [PATCH 6/9] workflow: Disable Python 3.7 CI, enable Python 3.12 CI --- .github/workflows/ci.yml | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 72795cdd0a..3c14a39546 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -32,6 +32,20 @@ jobs: rmdir panda3d-1.10.13 (cd thirdparty/darwin-libs-a && rm -rf rocket) + - name: Set up Python 3.12 + uses: actions/setup-python@v4 + with: + python-version: '3.12' + - name: Build Python 3.12 + shell: bash + run: | + python makepanda/makepanda.py --git-commit=${{github.sha}} --outputdir=built --everything --no-eigen --python-incdir="$pythonLocation/include" --python-libdir="$pythonLocation/lib" --verbose --threads=4 --windows-sdk=10 + - name: Test Python 3.12 + shell: bash + run: | + python -m pip install pytest + PYTHONPATH=built LD_LIBRARY_PATH=built/lib DYLD_LIBRARY_PATH=built/lib python -m pytest + - name: Set up Python 3.11 uses: actions/setup-python@v4 with: @@ -88,20 +102,6 @@ jobs: python -m pip install pytest PYTHONPATH=built LD_LIBRARY_PATH=built/lib DYLD_LIBRARY_PATH=built/lib python -m pytest - - name: Set up Python 3.7 - uses: actions/setup-python@v4 - with: - python-version: '3.7' - - name: Build Python 3.7 - shell: bash - run: | - python makepanda/makepanda.py --git-commit=${{github.sha}} --outputdir=built --everything --no-eigen --python-incdir="$pythonLocation/include" --python-libdir="$pythonLocation/lib" --verbose --threads=4 --windows-sdk=10 - - name: Test Python 3.7 - shell: bash - run: | - python -m pip install pytest - PYTHONPATH=built LD_LIBRARY_PATH=built/lib DYLD_LIBRARY_PATH=built/lib python -m pytest - - name: Make installer run: | python makepanda/makepackage.py --verbose --lzma From 2a5228b05f6692668bc819d3101c1ac6c4d8441a Mon Sep 17 00:00:00 2001 From: rdb Date: Fri, 13 Oct 2023 13:20:26 +0200 Subject: [PATCH 7/9] Fix compatibility with Python 3.12 by removing use of imp module Some modules (such as VFSImporter, and various modules in direct.p3d that depend on it) are still unavailable. --- direct/src/dist/FreezeTool.py | 197 +++++++++++++++++++------ direct/src/dist/commands.py | 5 +- direct/src/dist/icon.py | 265 ++++++++++++++++++++++++++++++++++ makepanda/test_imports.py | 36 +++-- tests/dist/test_FreezeTool.py | 64 ++++++++ 5 files changed, 503 insertions(+), 64 deletions(-) create mode 100644 direct/src/dist/icon.py create mode 100644 tests/dist/test_FreezeTool.py diff --git a/direct/src/dist/FreezeTool.py b/direct/src/dist/FreezeTool.py index 3cfdafe1e9..1ca9cf1c36 100644 --- a/direct/src/dist/FreezeTool.py +++ b/direct/src/dist/FreezeTool.py @@ -5,7 +5,6 @@ import modulefinder import sys import os import marshal -import imp import platform import struct import io @@ -18,6 +17,12 @@ import importlib from . import pefile +if sys.version_info >= (3, 4): + import _imp + from importlib import machinery +else: + import imp + # Temporary (?) try..except to protect against unbuilt p3extend_frozen. try: import p3extend_frozen @@ -26,6 +31,16 @@ except ImportError: from panda3d.core import * +# Old imp constants. +_PY_SOURCE = 1 +_PY_COMPILED = 2 +_C_EXTENSION = 3 +_PKG_DIRECTORY = 5 +_C_BUILTIN = 6 +_PY_FROZEN = 7 + +_PKG_NAMESPACE_DIRECTORY = object() + # Check to see if we are running python_d, which implies we have a # debug build, and we have to build the module with debug options. # This is only relevant on Windows. @@ -39,8 +54,11 @@ isDebugBuild = (python.lower().endswith('_d')) # NB. if encodings are removed, be sure to remove them from the shortcut in # deploy-stub.c. startupModules = [ - 'imp', 'encodings', 'encodings.*', + 'encodings', 'encodings.*', ] +if sys.version_info < (3, 12): + startupModules.insert(0, 'imp') + if sys.version_info >= (3, 0): # Modules specific to Python 3 startupModules += ['io', 'marshal', 'importlib.machinery', 'importlib.util'] @@ -885,12 +903,19 @@ class Freezer: # Suffix/extension for Python C extension modules if self.platform == PandaSystem.getPlatform(): - self.moduleSuffixes = imp.get_suffixes() + if sys.version_info >= (3, 4): + self.moduleSuffixes = ( + [(s, 'rb', _C_EXTENSION) for s in machinery.EXTENSION_SUFFIXES] + + [(s, 'rb', _PY_SOURCE) for s in machinery.SOURCE_SUFFIXES] + + [(s, 'rb', _PY_COMPILED) for s in machinery.BYTECODE_SUFFIXES] + ) + else: + self.moduleSuffixes = imp.get_suffixes() - # Set extension for Python files to binary mode - for i, suffix in enumerate(self.moduleSuffixes): - if suffix[2] == imp.PY_SOURCE: - self.moduleSuffixes[i] = (suffix[0], 'rb', imp.PY_SOURCE) + # Set extension for Python files to binary mode + for i, suffix in enumerate(self.moduleSuffixes): + if suffix[2] == _PY_SOURCE: + self.moduleSuffixes[i] = (suffix[0], 'rb', _PY_SOURCE) else: self.moduleSuffixes = [('.py', 'rb', 1), ('.pyc', 'rb', 2)] @@ -990,21 +1015,45 @@ class Freezer: # whatever--then just look for file on disk. That's usually # good enough. path = None - baseName = moduleName - if '.' in baseName: - parentName, baseName = moduleName.rsplit('.', 1) + name = moduleName + if '.' in name: + parentName, name = moduleName.rsplit('.', 1) path = self.getModulePath(parentName) if path is None: return None - try: - file, pathname, description = imp.find_module(baseName, path) - except ImportError: - return None + if sys.version_info < (3, 4): + try: + file, pathname, description = imp.find_module(name, path) + except ImportError: + return None - if not os.path.isdir(pathname): - return None - return [pathname] + if not os.path.isdir(pathname): + return None + return [pathname] + + if path is None: + if _imp.is_builtin(name) or _imp.is_frozen(name): + return None + + path = sys.path + + for entry in path: + package_directory = os.path.join(entry, name) + for suffix in ('.py', machinery.BYTECODE_SUFFIXES[0]): + package_file_name = '__init__' + suffix + file_path = os.path.join(package_directory, package_file_name) + if os.path.isfile(file_path): + return [package_directory] + + for suffix in machinery.EXTENSION_SUFFIXES + machinery.SOURCE_SUFFIXES + machinery.BYTECODE_SUFFIXES: + file_name = name + suffix + file_path = os.path.join(entry, file_name) + if os.path.isfile(file_path): + # Not a package. + return None + + return None def getModuleStar(self, moduleName): """ Looks for the indicated directory module and returns the @@ -1027,20 +1076,49 @@ class Freezer: # If it didn't work, just open the directory and scan for *.py # files. path = None - baseName = moduleName - if '.' in baseName: - parentName, baseName = moduleName.rsplit('.', 1) + name = moduleName + if '.' in name: + parentName, name = moduleName.rsplit('.', 1) path = self.getModulePath(parentName) if path is None: return None - try: - file, pathname, description = imp.find_module(baseName, path) - except ImportError: - return None + if sys.version_info < (3, 4): + try: + file, pathname, description = imp.find_module(name, path) + except ImportError: + return None - if not os.path.isdir(pathname): - return None + if not os.path.isdir(pathname): + return None + else: + if path is None: + if _imp.is_builtin(name) or _imp.is_frozen(name): + return None + + path = sys.path + + for entry in path: + package_directory = os.path.join(entry, name) + for suffix in ('.py', machinery.BYTECODE_SUFFIXES[0]): + package_file_name = '__init__' + suffix + file_path = os.path.join(package_directory, package_file_name) + if os.path.isfile(file_path): + pathname = package_directory + break + else: + for suffix in machinery.EXTENSION_SUFFIXES + machinery.SOURCE_SUFFIXES + machinery.BYTECODE_SUFFIXES: + file_name = name + suffix + file_path = os.path.join(entry, file_name) + if os.path.isfile(file_path): + # Not a package. + return None + else: + continue + + break # Break out of outer loop when breaking out of inner loop. + else: + return None # Scan the directory, looking for .py files. modules = [] @@ -1335,10 +1413,10 @@ class Freezer: ext = mdef.filename.getExtension() if ext == 'pyc' or ext == 'pyo': fp = open(pathname, 'rb') - stuff = ("", "rb", imp.PY_COMPILED) + stuff = ("", "rb", _PY_COMPILED) self.mf.load_module(mdef.moduleName, fp, pathname, stuff) else: - stuff = ("", "rb", imp.PY_SOURCE) + stuff = ("", "rb", _PY_SOURCE) if mdef.text: fp = io.StringIO(mdef.text) else: @@ -1434,10 +1512,10 @@ class Freezer: def __addPyc(self, multifile, filename, code, compressionLevel): if code: - data = imp.get_magic() + b'\0\0\0\0' - - if sys.version_info >= (3, 0): - data += b'\0\0\0\0' + if sys.version_info >= (3, 4): + data = importlib.util.MAGIC_NUMBER + b'\0\0\0\0\0\0\0\0' + else: + data = imp.get_magic() + b'\0\0\0\0' data += marshal.dumps(code) @@ -1634,7 +1712,11 @@ class Freezer: # trouble importing it as a builtin module. Synthesize a frozen # module that loads it as builtin. if '.' in moduleName and self.linkExtensionModules: - if sys.version_info >= (3, 2): + if sys.version_info >= (3, 5): + code = compile('import sys;del sys.modules["%s"];from importlib._bootstrap import _builtin_from_name;_builtin_from_name("%s")' % (moduleName, moduleName), moduleName, 'exec', optimize=self.optimize) + elif sys.version_info >= (3, 4): + code = compile('import sys;del sys.modules["%s"];import _imp;_imp.init_builtin("%s")' % (moduleName, moduleName), moduleName, 'exec', optimize=self.optimize) + elif sys.version_info >= (3, 2): code = compile('import sys;del sys.modules["%s"];import imp;imp.init_builtin("%s")' % (moduleName, moduleName), moduleName, 'exec', optimize=self.optimize) else: code = compile('import sys;del sys.modules["%s"];import imp;imp.init_builtin("%s")' % (moduleName, moduleName), moduleName, 'exec') @@ -1910,9 +1992,25 @@ class Freezer: if '.' in moduleName: if self.platform.startswith("macosx") and not use_console: # We write the Frameworks directory to sys.path[0]. - code = 'import sys;del sys.modules["%s"];import sys,os,imp;imp.load_dynamic("%s",os.path.join(sys.path[0], "%s%s"))' % (moduleName, moduleName, moduleName, modext) + direxpr = 'sys.path[0]' else: - code = 'import sys;del sys.modules["%s"];import sys,os,imp;imp.load_dynamic("%s",os.path.join(os.path.dirname(sys.executable), "%s%s"))' % (moduleName, moduleName, moduleName, modext) + direxpr = 'os.path.dirname(sys.executable)' + + if sys.version_info >= (3, 5): + code = \ + 'import sys;' \ + 'del sys.modules["{name}"];' \ + 'import sys,os;' \ + 'from importlib.machinery import ExtensionFileLoader,ModuleSpec;' \ + 'from importlib._bootstrap import _load;' \ + 'path=os.path.join({direxpr}, "{name}{ext}");' \ + '_load(ModuleSpec(name="{name}", loader=ExtensionFileLoader("{name}", path), origin=path))' \ + ''.format(name=moduleName, ext=modext, direxpr=direxpr) + elif sys.version_info >= (3, 4): + code = 'import sys;del sys.modules["%s"];import sys,os,_imp;_imp.load_dynamic("%s",os.path.join(%s, "%s%s"))' % (moduleName, moduleName, direxpr, moduleName, modext) + else: + code = 'import sys;del sys.modules["%s"];import sys,os,imp;imp.load_dynamic("%s",os.path.join(%s, "%s%s"))' % (moduleName, moduleName, direxpr, moduleName, modext) + if sys.version_info >= (3, 2): code = compile(code, moduleName, 'exec', optimize=self.optimize) else: @@ -2362,9 +2460,6 @@ class Freezer: return True -_PKG_NAMESPACE_DIRECTORY = object() - - class PandaModuleFinder(modulefinder.ModuleFinder): def __init__(self, *args, **kw): @@ -2375,7 +2470,17 @@ class PandaModuleFinder(modulefinder.ModuleFinder): :param debug: an integer indicating the level of verbosity """ - self.suffixes = kw.pop('suffixes', imp.get_suffixes()) + if 'suffixes' in kw: + self.suffixes = kw.pop('suffixes') + elif sys.version_info >= (3, 4): + self.suffixes = ( + [(s, 'rb', _C_EXTENSION) for s in machinery.EXTENSION_SUFFIXES] + + [(s, 'r', _PY_SOURCE) for s in machinery.SOURCE_SUFFIXES] + + [(s, 'rb', _PY_COMPILED) for s in machinery.BYTECODE_SUFFIXES] + ) + else: + self.suffixes = imp.get_suffixes() + self.optimize = kw.pop('optimize', -1) modulefinder.ModuleFinder.__init__(self, *args, **kw) @@ -2475,7 +2580,7 @@ class PandaModuleFinder(modulefinder.ModuleFinder): suffix, mode, type = file_info self.msgin(2, "load_module", fqname, fp and "fp", pathname) - if type == imp.PKG_DIRECTORY: + if type == _PKG_DIRECTORY: m = self.load_package(fqname, pathname) self.msgout(2, "load_module ->", m) return m @@ -2489,7 +2594,7 @@ class PandaModuleFinder(modulefinder.ModuleFinder): m.__path__ = pathname return m - if type == imp.PY_SOURCE: + if type == _PY_SOURCE: if fqname in overrideModules: # This module has a custom override. code = overrideModules[fqname] @@ -2516,7 +2621,7 @@ class PandaModuleFinder(modulefinder.ModuleFinder): co = compile(code, pathname, 'exec', optimize=self.optimize) else: co = compile(code, pathname, 'exec') - elif type == imp.PY_COMPILED: + elif type == _PY_COMPILED: if sys.version_info >= (3, 7): try: data = fp.read() @@ -2681,12 +2786,12 @@ class PandaModuleFinder(modulefinder.ModuleFinder): # If we have a custom override for this module, we know we have it. if fullname in overrideModules: - return (None, '', ('.py', 'r', imp.PY_SOURCE)) + return (None, '', ('.py', 'r', _PY_SOURCE)) # If no search path is given, look for a built-in module. if path is None: if name in sys.builtin_module_names: - return (None, None, ('', '', imp.C_BUILTIN)) + return (None, None, ('', '', _C_BUILTIN)) path = self.path @@ -2718,7 +2823,7 @@ class PandaModuleFinder(modulefinder.ModuleFinder): for suffix, mode, _ in self.suffixes: init = os.path.join(basename, '__init__' + suffix) if self._open_file(init, mode): - return (None, basename, ('', '', imp.PKG_DIRECTORY)) + return (None, basename, ('', '', _PKG_DIRECTORY)) # This may be a namespace package. if self._dir_exists(basename): @@ -2730,7 +2835,7 @@ class PandaModuleFinder(modulefinder.ModuleFinder): # Only if we're not looking on a particular path, though. if p3extend_frozen and p3extend_frozen.is_frozen_module(name): # It's a frozen module. - return (None, name, ('', '', imp.PY_FROZEN)) + return (None, name, ('', '', _PY_FROZEN)) # If we found folders on the path with this module name without an # __init__.py file, we should consider this a namespace package. diff --git a/direct/src/dist/commands.py b/direct/src/dist/commands.py index a37f043849..e402b3c016 100644 --- a/direct/src/dist/commands.py +++ b/direct/src/dist/commands.py @@ -15,7 +15,6 @@ import re import shutil import stat import struct -import imp import string import time import tempfile @@ -25,7 +24,7 @@ import distutils.log from . import FreezeTool from . import pefile -from direct.p3d.DeploymentTools import Icon +from .icon import Icon import panda3d.core as p3d @@ -912,7 +911,7 @@ class build_apps(setuptools.Command): for mod in freezer.getModuleDefs() if mod[1].filename }) for suffix in freezer.moduleSuffixes: - if suffix[2] == imp.C_EXTENSION: + if suffix[2] == 3: # imp.C_EXTENSION: ext_suffixes.add(suffix[0]) for appname, scriptname in self.gui_apps.items(): diff --git a/direct/src/dist/icon.py b/direct/src/dist/icon.py new file mode 100644 index 0000000000..4a34cb95af --- /dev/null +++ b/direct/src/dist/icon.py @@ -0,0 +1,265 @@ +from direct.directnotify.DirectNotifyGlobal import directNotify +from panda3d.core import PNMImage, Filename, PNMFileTypeRegistry, StringStream +import struct + + +class Icon: + """ This class is used to create an icon for various platforms. """ + notify = directNotify.newCategory("Icon") + + def __init__(self): + self.images = {} + + def addImage(self, image): + """ Adds an image to the icon. Returns False on failure, True on success. + Only one image per size can be loaded, and the image size must be square. """ + + if not isinstance(image, PNMImage): + fn = image + if not isinstance(fn, Filename): + fn = Filename.fromOsSpecific(fn) + + image = PNMImage() + if not image.read(fn): + Icon.notify.warning("Image '%s' could not be read" % fn.getBasename()) + return False + + if image.getXSize() != image.getYSize(): + Icon.notify.warning("Ignoring image without square size") + return False + + self.images[image.getXSize()] = image + + return True + + def generateMissingImages(self): + """ Generates image sizes that should be present but aren't by scaling + from the next higher size. """ + + for required_size in (256, 128, 48, 32, 16): + if required_size in self.images: + continue + + sizes = sorted(self.images.keys()) + if required_size * 2 in sizes: + from_size = required_size * 2 + else: + from_size = 0 + for from_size in sizes: + if from_size > required_size: + break + + if from_size > required_size: + Icon.notify.warning("Generating %dx%d icon by scaling down %dx%d image" % (required_size, required_size, from_size, from_size)) + + image = PNMImage(required_size, required_size) + image.setColorType(self.images[from_size].getColorType()) + image.quickFilterFrom(self.images[from_size]) + self.images[required_size] = image + else: + Icon.notify.warning("Cannot generate %dx%d icon; no higher resolution image available" % (required_size, required_size)) + + def _write_bitmap(self, fp, image, size, bpp): + """ Writes the bitmap header and data of an .ico file. """ + + fp.write(struct.pack('> 3) & 3) + for y in range(size): + mask = 0 + num_bits = 7 + for x in range(size): + a = image.get_alpha_val(x, size - y - 1) + if a <= 1: + mask |= (1 << num_bits) + num_bits -= 1 + if num_bits < 0: + fp.write(struct.pack('> 3 + if andsize % 4 != 0: + andsize += 4 - (andsize % 4) + fp.write(b'\x00' * (andsize * size)) + + def makeICO(self, fn): + """ Writes the images to a Windows ICO file. Returns True on success. """ + + if not isinstance(fn, Filename): + fn = Filename.fromOsSpecific(fn) + fn.setBinary() + + # ICO files only support resolutions up to 256x256. + count = 0 + for size in self.images: + if size < 256: + count += 1 + if size <= 256: + count += 1 + dataoffs = 6 + count * 16 + + ico = open(fn, 'wb') + ico.write(struct.pack('= 256: + continue + ico.write(struct.pack('> 3 + if andsize % 4 != 0: + andsize += 4 - (andsize % 4) + datasize = 40 + 256 * 4 + (xorsize + andsize) * size + + ico.write(struct.pack(' 256: + continue + elif size == 256: + ico.write(b'\0\0') + else: + ico.write(struct.pack('> 3 + if andsize % 4 != 0: + andsize += 4 - (andsize % 4) + datasize = 40 + (xorsize + andsize) * size + + ico.write(struct.pack('I', len(pngdata))) + icns.write(pngdata) + + elif size in icon_types: + # If it has an alpha channel, we write out a mask too. + if image.hasAlpha(): + icns.write(mask_types[size]) + icns.write(struct.pack('>I', size * size + 8)) + + for y in range(size): + for x in range(size): + icns.write(struct.pack('I', size * size * 4 + 8)) + + for y in range(size): + for x in range(size): + r, g, b = image.getXel(x, y) + icns.write(struct.pack('>BBBB', 0, int(r * 255), int(g * 255), int(b * 255))) + + length = icns.tell() + icns.seek(4) + icns.write(struct.pack('>I', length)) + icns.close() + + return True diff --git a/makepanda/test_imports.py b/makepanda/test_imports.py index a9b22943c8..1ba808eca4 100644 --- a/makepanda/test_imports.py +++ b/makepanda/test_imports.py @@ -6,15 +6,19 @@ import os, importlib # This will print out imports on the command line. import direct.showbase.VerboseImport +import sys +if sys.version_info >= (3, 4): + from importlib import machinery + extensions = machinery.EXTENSION_SUFFIXES + machinery.SOURCE_SUFFIXES + machinery.BYTECODE_SUFFIXES +else: + import imp + extensions = set() + for suffix in imp.get_suffixes(): + extensions.add(suffix[0]) -import imp import panda3d dir = os.path.dirname(panda3d.__file__) -extensions = set() -for suffix in imp.get_suffixes(): - extensions.add(suffix[0]) - for basename in os.listdir(dir): module = basename.split('.', 1)[0] ext = basename[len(module):] @@ -159,18 +163,19 @@ import direct.interval.ProjectileIntervalTest import direct.interval.SoundInterval import direct.interval.TestInterval import direct.motiontrail.MotionTrail -import direct.p3d.AppRunner -import direct.p3d.DWBPackageInstaller -import direct.p3d.DeploymentTools +if sys.version_info < (3, 12): + import direct.p3d.AppRunner + import direct.p3d.DWBPackageInstaller + import direct.p3d.DeploymentTools + import direct.p3d.HostInfo + import direct.p3d.JavaScript + import direct.p3d.PackageInfo + import direct.p3d.PackageInstaller + import direct.p3d.PackageMerger + import direct.p3d.Packager import direct.p3d.FileSpec -import direct.p3d.HostInfo import direct.p3d.InstalledHostData import direct.p3d.InstalledPackageData -import direct.p3d.JavaScript -import direct.p3d.PackageInfo -import direct.p3d.PackageInstaller -import direct.p3d.PackageMerger -import direct.p3d.Packager import direct.p3d.PatchMaker import direct.p3d.ScanDirectoryNode import direct.p3d.SeqValue @@ -231,7 +236,8 @@ import direct.showbase.ShowBase import direct.showbase.TaskThreaded import direct.showbase.ThreeUpShow import direct.showbase.Transitions -import direct.showbase.VFSImporter +if sys.version_info < (3, 12): + import direct.showbase.VFSImporter import direct.showbase.WxGlobal import direct.showutil.BuildGeometry import direct.showutil.Effects diff --git a/tests/dist/test_FreezeTool.py b/tests/dist/test_FreezeTool.py new file mode 100644 index 0000000000..898c53e7e1 --- /dev/null +++ b/tests/dist/test_FreezeTool.py @@ -0,0 +1,64 @@ +from direct.dist.FreezeTool import Freezer, PandaModuleFinder +import sys + + +def test_Freezer_moduleSuffixes(): + freezer = Freezer() + + for suffix, mode, type in freezer.moduleSuffixes: + if type == 2: # imp.PY_SOURCE + assert mode == 'rb' + + +def test_Freezer_getModulePath_getModuleStar(tmp_path): + # Package 1 can be imported + package1 = tmp_path / "package1" + package1.mkdir() + (package1 / "submodule1.py").write_text("") + (package1 / "__init__.py").write_text("") + + # Package 2 can not be imported + package2 = tmp_path / "package2" + package2.mkdir() + (package2 / "submodule2.py").write_text("") + (package2 / "__init__.py").write_text("raise ImportError\n") + + # Module 1 can be imported + (tmp_path / "module1.py").write_text("") + + # Module 2 can not be imported + (tmp_path / "module2.py").write_text("raise ImportError\n") + + # Module 3 has a custom __path__ and __all__ + (tmp_path / "module3.py").write_text("__path__ = ['foobar']\n" + "__all__ = ['test']\n") + + backup = sys.path + try: + # Don't fail if first item on path does not exist + sys.path = [str(tmp_path / "nonexistent"), str(tmp_path)] + + freezer = Freezer() + assert freezer.getModulePath("nonexist") == None + assert freezer.getModulePath("package1") == [str(package1)] + assert freezer.getModulePath("package2") == [str(package2)] + assert freezer.getModulePath("package1.submodule1") == None + assert freezer.getModulePath("package1.nonexist") == None + assert freezer.getModulePath("package2.submodule2") == None + assert freezer.getModulePath("package2.nonexist") == None + assert freezer.getModulePath("module1") == None + assert freezer.getModulePath("module2") == None + assert freezer.getModulePath("module3") == ['foobar'] + + assert freezer.getModuleStar("nonexist") == None + assert freezer.getModuleStar("package1") == ['submodule1'] + assert freezer.getModuleStar("package2") == ['submodule2'] + assert freezer.getModuleStar("package1.submodule1") == None + assert freezer.getModuleStar("package1.nonexist") == None + assert freezer.getModuleStar("package2.submodule2") == None + assert freezer.getModuleStar("package2.nonexist") == None + assert freezer.getModuleStar("module1") == None + assert freezer.getModuleStar("module2") == None + assert freezer.getModuleStar("module3") == ['test'] + finally: + sys.path = backup From e4738194d50d433f9e4d1c5b59413459edc24f78 Mon Sep 17 00:00:00 2001 From: rdb Date: Fri, 13 Oct 2023 13:21:48 +0200 Subject: [PATCH 8/9] pfreeze: use clang, fix missing path with non-system Python on macOS --- direct/src/dist/FreezeTool.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/direct/src/dist/FreezeTool.py b/direct/src/dist/FreezeTool.py index 1ca9cf1c36..ce0aa1ad38 100644 --- a/direct/src/dist/FreezeTool.py +++ b/direct/src/dist/FreezeTool.py @@ -333,10 +333,15 @@ class CompilationEnvironment: self.arch = '-arch x86_64' elif proc in ('arm64', 'aarch64'): self.arch = '-arch arm64' - self.compileObjExe = "gcc -c %(arch)s -o %(basename)s.o -O2 -I%(pythonIPath)s %(filename)s" - self.compileObjDll = "gcc -fPIC -c %(arch)s -o %(basename)s.o -O2 -I%(pythonIPath)s %(filename)s" - self.linkExe = "gcc %(arch)s -o %(basename)s %(basename)s.o -framework Python" - self.linkDll = "gcc %(arch)s -undefined dynamic_lookup -bundle -o %(basename)s.so %(basename)s.o" + self.compileObjExe = "clang -c %(arch)s -o %(basename)s.o -O2 -I%(pythonIPath)s %(filename)s" + self.compileObjDll = "clang -fPIC -c %(arch)s -o %(basename)s.o -O2 -I%(pythonIPath)s %(filename)s" + self.linkExe = "clang %(arch)s -o %(basename)s %(basename)s.o" + if '/Python.framework/' in self.PythonIPath: + framework_dir = self.PythonIPath.split("/Python.framework/", 1)[0] + if framework_dir != "/System/Library/Frameworks": + self.linkExe += " -F " + framework_dir + self.linkExe += " -framework Python" + self.linkDll = "clang %(arch)s -undefined dynamic_lookup -bundle -o %(basename)s.so %(basename)s.o" else: # Unix From bf456baa35bd2213f7ae8b8adc27c341dc2c0756 Mon Sep 17 00:00:00 2001 From: rdb Date: Fri, 13 Oct 2023 13:35:40 +0200 Subject: [PATCH 9/9] workflow: Skip Python 3.12 tests on Windows for now Until we've added Python 3.12 to thirdparty packages --- .github/workflows/ci.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3c14a39546..64978eaf1c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -33,14 +33,17 @@ jobs: (cd thirdparty/darwin-libs-a && rm -rf rocket) - name: Set up Python 3.12 + if: matrix.os != 'windows-2019' uses: actions/setup-python@v4 with: python-version: '3.12' - name: Build Python 3.12 + if: matrix.os != 'windows-2019' shell: bash run: | python makepanda/makepanda.py --git-commit=${{github.sha}} --outputdir=built --everything --no-eigen --python-incdir="$pythonLocation/include" --python-libdir="$pythonLocation/lib" --verbose --threads=4 --windows-sdk=10 - name: Test Python 3.12 + if: matrix.os != 'windows-2019' shell: bash run: | python -m pip install pytest