diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f4bd30ac76..326e8a2873 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -355,6 +355,23 @@ jobs: rmdir panda3d-1.10.14 (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 -r requirements-test.txt + 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: 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']) diff --git a/direct/src/dist/FreezeTool.py b/direct/src/dist/FreezeTool.py index 84189ac77c..3a1b6487b8 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 @@ -13,6 +12,7 @@ import sysconfig import zipfile import importlib import warnings +from importlib import machinery from . import pefile @@ -24,6 +24,16 @@ except ImportError: from panda3d.core import Filename, Multifile, PandaSystem, StringStream +# 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. @@ -37,7 +47,7 @@ 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.*', 'io', 'marshal', 'importlib.machinery', + 'encodings', 'encodings.*', 'io', 'marshal', 'importlib.machinery', 'importlib.util', ] @@ -262,10 +272,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 @@ -897,12 +912,11 @@ class Freezer: # Suffix/extension for Python C extension modules if self.platform == PandaSystem.getPlatform(): - suffixes = imp.get_suffixes() - - # Set extension for Python files to binary mode - for i, suffix in enumerate(suffixes): - if suffix[2] == imp.PY_SOURCE: - suffixes[i] = (suffix[0], 'rb', imp.PY_SOURCE) + suffixes = ( + [(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: suffixes = [('.py', 'rb', 1), ('.pyc', 'rb', 2)] @@ -1316,10 +1330,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: @@ -1415,7 +1429,7 @@ class Freezer: def __addPyc(self, multifile, filename, code, compressionLevel): if code: - data = imp.get_magic() + b'\0\0\0\0\0\0\0\0' + data = importlib.util.MAGIC_NUMBER + b'\0\0\0\0\0\0\0\0' data += marshal.dumps(code) stream = StringStream(data) @@ -1605,7 +1619,7 @@ class Freezer: # trouble importing it as a builtin module. Synthesize a frozen # module that loads it as builtin. if '.' in moduleName and self.linkExtensionModules: - code = compile('import sys;del sys.modules["%s"];import imp;imp.init_builtin("%s")' % (moduleName, moduleName), moduleName, 'exec', optimize=self.optimize) + 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) code = marshal.dumps(code) mangledName = self.mangleName(moduleName) moduleDefs.append(self.makeModuleDef(mangledName, code)) @@ -1887,9 +1901,19 @@ class Freezer: if '.' in moduleName and not self.platform.startswith('android'): 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)' + + code = \ + f'import sys;' \ + f'del sys.modules["{moduleName}"];' \ + f'import sys,os;' \ + f'from importlib.machinery import ExtensionFileLoader,ModuleSpec;' \ + f'from importlib._bootstrap import _load;' \ + f'path=os.path.join({direxpr}, "{moduleName}{modext}");' \ + f'_load(ModuleSpec(name="{moduleName}", loader=ExtensionFileLoader("{moduleName}", path), origin=path))' + code = compile(code, moduleName, 'exec', optimize=self.optimize) code = marshal.dumps(code) moduleList.append((moduleName, len(pool), len(code))) @@ -2400,9 +2424,6 @@ class Freezer: return True -_PKG_NAMESPACE_DIRECTORY = object() - - class PandaModuleFinder(modulefinder.ModuleFinder): def __init__(self, *args, **kw): @@ -2415,7 +2436,12 @@ class PandaModuleFinder(modulefinder.ModuleFinder): self.builtin_module_names = kw.pop('builtin_module_names', sys.builtin_module_names) - self.suffixes = kw.pop('suffixes', imp.get_suffixes()) + self.suffixes = kw.pop('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] + )) + self.optimize = kw.pop('optimize', -1) modulefinder.ModuleFinder.__init__(self, *args, **kw) @@ -2563,7 +2589,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 @@ -2574,7 +2600,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] @@ -2598,7 +2624,7 @@ class PandaModuleFinder(modulefinder.ModuleFinder): code += b'\n' if isinstance(code, bytes) else '\n' co = compile(code, pathname, 'exec', optimize=self.optimize) - elif type == imp.PY_COMPILED: + elif type == _PY_COMPILED: if sys.version_info >= (3, 7): try: data = fp.read() @@ -2752,11 +2778,11 @@ 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)) # It's built into the interpreter. if fullname in self.builtin_module_names: - return (None, None, ('', '', imp.C_BUILTIN)) + return (None, None, ('', '', _C_BUILTIN)) # If no search path is given, look for a built-in module. if path is None: @@ -2806,7 +2832,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): @@ -2818,7 +2844,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 3bfafe77c3..9ee83736cf 100644 --- a/direct/src/dist/commands.py +++ b/direct/src/dist/commands.py @@ -13,7 +13,6 @@ import re import shutil import stat import struct -import imp import string import tempfile @@ -1068,7 +1067,7 @@ class build_apps(setuptools.Command): freezer_extras.update(freezer.extras) freezer_modules.update(freezer.getAllModuleNames()) for suffix in freezer.mf.suffixes: - 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/dtool/src/interrogatedb/py_compat.h b/dtool/src/interrogatedb/py_compat.h index 960ca8e9aa..2a340ff060 100644 --- a/dtool/src/interrogatedb/py_compat.h +++ b/dtool/src/interrogatedb/py_compat.h @@ -241,6 +241,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 */ #endif // HAVE_PYTHON diff --git a/makepanda/installpanda.py b/makepanda/installpanda.py index 020dda54c6..6ac2474a50 100644 --- a/makepanda/installpanda.py +++ b/makepanda/installpanda.py @@ -12,9 +12,7 @@ import os import sys from optparse import OptionParser from makepandacore import * - -# DO NOT CHANGE TO sysconfig - see GitHub issue #1230 -from distutils.sysconfig import get_python_lib +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 c240b05206..8bca2e31e1 100755 --- a/makepanda/makepackage.py +++ b/makepanda/makepackage.py @@ -942,8 +942,7 @@ def MakeInstallerAndroid(version, **kwargs): shutil.copy(os.path.join(source_dir, base), target) # Copy the Python standard library to the .apk as well. - # DO NOT CHANGE TO sysconfig - see #1230 - 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 e1d4dec612..d58513ce46 100755 --- a/makepanda/makepanda.py +++ b/makepanda/makepanda.py @@ -30,12 +30,10 @@ except: print("Please install the development package of Python and try again.") exit(1) -if sys.version_info >= (3, 10): - from sysconfig import get_platform -else: - from distutils.util import get_platform from makepandacore import * +from sysconfig import get_platform + try: import zlib except: diff --git a/makepanda/makepandacore.py b/makepanda/makepandacore.py index e7a93b48df..0e49a28e02 100644 --- a/makepanda/makepandacore.py +++ b/makepanda/makepandacore.py @@ -21,6 +21,7 @@ import sys import threading import _thread as thread import time +import locations SUFFIX_INC = [".cxx",".cpp",".c",".h",".I",".yxx",".lxx",".mm",".rc",".r"] SUFFIX_DLL = [".dll",".dlo",".dle",".dli",".dlm",".mll",".exe",".pyd",".ocx"] @@ -2195,7 +2196,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) @@ -2220,19 +2221,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"])) @@ -3386,7 +3387,7 @@ def GetExtensionSuffix(): def GetPythonABI(): if not CrossCompiling(): - soabi = sysconfig.get_config_var('SOABI') + soabi = locations.get_config_var('SOABI') if soabi: return soabi @@ -3507,8 +3508,8 @@ def GetCurrentPythonVersionInfo(): "soabi": GetPythonABI(), "ext_suffix": GetExtensionSuffix(), "executable": sys.executable, - "purelib": sysconfig.get_python_lib(False), - "platlib": sysconfig.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 5b647e6a7c..5879b2546a 100644 --- a/makepanda/makewheel.py +++ b/makepanda/makewheel.py @@ -11,10 +11,11 @@ import tempfile import subprocess import time import struct -from sysconfig import get_platform, get_config_var from optparse import OptionParser from base64 import urlsafe_b64encode from makepandacore import LocateBinary, GetExtensionSuffix, SetVerbose, GetVerbose, GetMetadataValue, CrossCompiling, GetThirdpartyDir, SDK, GetStrip +from locations import get_config_var +from sysconfig import get_platform def get_abi_tag(): 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 1777699bcd..87c4e8f629 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"; diff --git a/panda/src/putil/bitArray_ext.cxx b/panda/src/putil/bitArray_ext.cxx index ec3a95b800..2a48951040 100644 --- a/panda/src/putil/bitArray_ext.cxx +++ b/panda/src/putil/bitArray_ext.cxx @@ -20,7 +20,7 @@ */ void Extension:: __init__(PyObject *init_value) { - 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; } @@ -76,7 +76,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 f63fbc5182..a40b91355d 100644 --- a/panda/src/putil/doubleBitMask_ext.I +++ b/panda/src/putil/doubleBitMask_ext.I @@ -17,7 +17,7 @@ template INLINE void Extension >:: __init__(PyObject *init_value) { - 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/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') diff --git a/tests/dist/test_FreezeTool.py b/tests/dist/test_FreezeTool.py new file mode 100644 index 0000000000..32ae5fd2bc --- /dev/null +++ b/tests/dist/test_FreezeTool.py @@ -0,0 +1,58 @@ +from direct.dist.FreezeTool import Freezer, PandaModuleFinder +import sys + + +def test_Freezer_moduleSuffixes(): + freezer = Freezer() + + for suffix, mode, type in freezer.mf.suffixes: + 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") + + 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.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 + finally: + sys.path = backup diff --git a/tests/putil/test_bitarray.py b/tests/putil/test_bitarray.py index be19c6e98d..81f058f904 100644 --- a/tests/putil/test_bitarray.py +++ b/tests/putil/test_bitarray.py @@ -118,6 +118,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)) + ba = BitArray(94187049178237918273981729127381723) assert ba == pickle.loads(pickle.dumps(ba, -1)) diff --git a/tests/test_imports.py b/tests/test_imports.py index 58b146e589..0fba7bff13 100644 --- a/tests/test_imports.py +++ b/tests/test_imports.py @@ -2,6 +2,7 @@ # missing imports. It is useful for a quick and dirty test to make sure # that there are no obvious build issues. import pytest +import sys # This will print out imports on the command line. #import direct.showbase.VerboseImport @@ -9,7 +10,7 @@ import pytest def test_imports_panda3d(): - import importlib, os, sys + import importlib, os import panda3d # Look for panda3d.* modules in builtins - pfreeze might put them there. @@ -165,7 +166,8 @@ def test_imports_direct(): 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