From d1e4b4357212b54da4b64a02feea04d90f1f004c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rados=C5=82aw=20Szkodzi=C5=84ski?= Date: Sat, 6 Jan 2024 10:43:01 +0100 Subject: [PATCH 1/2] Fix Windows build and runtime on Python 3.11.4 Fixes: - Runtime on Python 3.11 - Setuptools, tested with 3.11.4 - Missing pywin32 dependency - Lock file mishandling on Windows Not included yet: - Executable creation, needs bigger rework of either pyinstaller or py2exe --- DisplayCAL/config.py | 2 -- DisplayCAL/main.py | 47 +++++++++++++++--------------- DisplayCAL/postinstall.py | 2 +- DisplayCAL/setup.py | 8 ++++-- DisplayCAL/util_os.py | 60 +++++++++++++++++++-------------------- pyproject.toml | 3 +- util/winmanifest.py | 6 ++-- 7 files changed, 65 insertions(+), 63 deletions(-) diff --git a/DisplayCAL/config.py b/DisplayCAL/config.py index f5743952..a7fdd791 100644 --- a/DisplayCAL/config.py +++ b/DisplayCAL/config.py @@ -2226,7 +2226,5 @@ def writecfg(which="user", worker=None, module=None, options=(), cfg=cfg): if not cafile: # Use our bundled CA file cafile = get_data_path("cacert.pem") - if cafile: - cafile = cafile.encode(fs_enc, "replace") if cafile: os.environ["SSL_CERT_FILE"] = cafile diff --git a/DisplayCAL/main.py b/DisplayCAL/main.py index 219ec7e8..2fe91119 100644 --- a/DisplayCAL/main.py +++ b/DisplayCAL/main.py @@ -70,7 +70,7 @@ from DisplayCAL.util_os import FileLock, LockingError, UnlockingError if sys.platform == "win32": - from util_win import win_ver + from DisplayCAL.util_win import win_ver import ctypes @@ -392,8 +392,9 @@ def _main(module, name, app_lock_file_name, probe_ports=True): handle_error(lang.getstr("app.otherinstance", name)) # Exit return + lock.unlock() # Use exclusive lock during app startup - with lock: + with AppLock(app_lock_file_name, "a+", True, True) as lock: # Create listening socket appsocket = AppSocket() if appsocket: @@ -513,27 +514,27 @@ def _main(module, name, app_lock_file_name, probe_ports=True): % ";".join(script).encode(fs_enc), ] ) - # Initialize & run - if module == "3DLUT-maker": - from DisplayCAL.wxLUT3DFrame import main - elif module == "curve-viewer": - from DisplayCAL.wxLUTViewer import main - elif module == "profile-info": - from DisplayCAL.wxProfileInfo import main - elif module == "scripting-client": - from DisplayCAL.wxScriptingClient import main - elif module == "synthprofile": - from DisplayCAL.wxSynthICCFrame import main - elif module == "testchart-editor": - from DisplayCAL.wxTestchartEditor import main - elif module == "VRML-to-X3D-converter": - from DisplayCAL.wxVRML2X3D import main - elif module == "apply-profiles": - from DisplayCAL.profile_loader import main - else: - from DisplayCAL.display_cal import main - # Run main after releasing lock - main() + # Initialize & run + if module == "3DLUT-maker": + from DisplayCAL.wxLUT3DFrame import main + elif module == "curve-viewer": + from DisplayCAL.wxLUTViewer import main + elif module == "profile-info": + from DisplayCAL.wxProfileInfo import main + elif module == "scripting-client": + from DisplayCAL.wxScriptingClient import main + elif module == "synthprofile": + from DisplayCAL.wxSynthICCFrame import main + elif module == "testchart-editor": + from DisplayCAL.wxTestchartEditor import main + elif module == "VRML-to-X3D-converter": + from DisplayCAL.wxVRML2X3D import main + elif module == "apply-profiles": + from DisplayCAL.profile_loader import main + else: + from DisplayCAL.display_cal import main + # Run main after releasing lock + main() def main(module=None): diff --git a/DisplayCAL/postinstall.py b/DisplayCAL/postinstall.py index 0c66cec3..f071a60f 100644 --- a/DisplayCAL/postinstall.py +++ b/DisplayCAL/postinstall.py @@ -83,7 +83,7 @@ def file_created(path): installed_files.extend(line.rstrip("\n") for line in recordfile) recordfile.close() try: - path.decode("ASCII") + path.encode("ASCII") except (UnicodeDecodeError, UnicodeEncodeError): # the contents of the record file used by distutils # must be ASCII GetShortPathName allows us to avoid diff --git a/DisplayCAL/setup.py b/DisplayCAL/setup.py index 90765dca..06ac0205 100644 --- a/DisplayCAL/setup.py +++ b/DisplayCAL/setup.py @@ -197,9 +197,9 @@ def add_lib_excludes(key, excludebits): config["excludes"][key].extend([f"{name}.lib{exclude}", f"lib{exclude}"]) for exclude in ("32", "64"): - for pycompat in ("38", "39", "310"): + for pycompat in ("38", "39", "310", "311", "312"): if key == "win32" and ( - pycompat == sys.version[0] + sys.version[2] or exclude == excludebits[0] + pycompat == str(sys.version_info[0]) + str(sys.version_info[2]) or exclude == excludebits[0] ): continue config["excludes"][key].extend( @@ -955,7 +955,7 @@ def findall( packages = [name, f"{name}.lib", f"{name}.lib.agw"] if sdist: # For source distributions we want all libraries - for pycompat in ("38", "39", "310"): + for pycompat in ("38", "39", "310", "311", "312"): packages.extend( [ f"{name}.lib{bits}", @@ -1265,6 +1265,8 @@ def copy_package_data(self, package, target_dir): "mswsock.dll", "urlmon.dll", "w9xpopen.exe", + "gdiplus.dll", + "mfc90.dll", ], "excludes": config["excludes"]["all"] + config["excludes"]["win32"], "bundle_files": 3 if wx.VERSION >= (2, 8, 10, 1) else 1, diff --git a/DisplayCAL/util_os.py b/DisplayCAL/util_os.py index 5eee56ca..dc4ce232 100644 --- a/DisplayCAL/util_os.py +++ b/DisplayCAL/util_os.py @@ -104,95 +104,95 @@ def open(path, *args, **kwargs): _access = os.access - def access(path, mode): - return _access(make_win32_compatible_long_path(path), mode) + def access(path, mode, *args, **kwargs): + return _access(make_win32_compatible_long_path(path), mode, *args, **kwargs) os.access = access _exists = os.path.exists - def exists(path): - return _exists(make_win32_compatible_long_path(path)) + def exists(path, *args, **kwargs): + return _exists(make_win32_compatible_long_path(path), *args, **kwargs) os.path.exists = exists _isdir = os.path.isdir - def isdir(path): - return _isdir(make_win32_compatible_long_path(path)) + def isdir(path, *args, **kwargs): + return _isdir(make_win32_compatible_long_path(path), *args, **kwargs) os.path.isdir = isdir _isfile = os.path.isfile - def isfile(path): - return _isfile(make_win32_compatible_long_path(path)) + def isfile(path, *args, **kwargs): + return _isfile(make_win32_compatible_long_path(path), *args, **kwargs) os.path.isfile = isfile - def listdir(path): - return _listdir(make_win32_compatible_long_path(path)) + def listdir(path, *args, **kwargs): + return _listdir(make_win32_compatible_long_path(path), *args, **kwargs) _lstat = os.lstat - def lstat(path): - return _lstat(make_win32_compatible_long_path(path)) + def lstat(path, *args, **kwargs): + return _lstat(make_win32_compatible_long_path(path), *args, **kwargs) os.lstat = lstat _mkdir = os.mkdir - def mkdir(path, mode=0o777): - return _mkdir(make_win32_compatible_long_path(path, 247), mode) + def mkdir(path, mode=0o777, *args, **kwargs): + return _mkdir(make_win32_compatible_long_path(path, 247), mode, *args, **kwargs) os.mkdir = mkdir _makedirs = os.makedirs - def makedirs(path, mode=0o777): - return _makedirs(make_win32_compatible_long_path(path, 247), mode) + def makedirs(path, mode=0o777, *args, **kwargs): + return _makedirs(make_win32_compatible_long_path(path, 247), mode, *args, **kwargs) os.makedirs = makedirs _remove = os.remove - def remove(path): - return _remove(make_win32_compatible_long_path(path)) + def remove(path, *args, **kwargs): + return _remove(make_win32_compatible_long_path(path), *args, **kwargs) os.remove = retry_sharing_violation_factory(remove) _rename = os.rename - def rename(src, dst): + def rename(src, dst, *args, **kwargs): src, dst = [make_win32_compatible_long_path(path) for path in (src, dst)] - return _rename(src, dst) + return _rename(src, dst, *args, **kwargs) os.rename = retry_sharing_violation_factory(rename) _stat = os.stat - def stat(path): - return _stat(make_win32_compatible_long_path(path)) + def stat(path, *args, **kwargs): + return _stat(make_win32_compatible_long_path(path), *args, **kwargs) os.stat = stat _unlink = os.unlink - def unlink(path): - return _unlink(make_win32_compatible_long_path(path)) + def unlink(path, *args, **kwargs): + return _unlink(make_win32_compatible_long_path(path), *args, **kwargs) os.unlink = retry_sharing_violation_factory(unlink) _GetShortPathName = win32api.GetShortPathName - def GetShortPathName(path): - return _GetShortPathName(make_win32_compatible_long_path(path)) + def GetShortPathName(path, *args, **kwargs): + return _GetShortPathName(make_win32_compatible_long_path(path), *args, **kwargs) win32api.GetShortPathName = GetShortPathName else: - def listdir(path): - paths = _listdir(path) + def listdir(path, *args, **kwargs): + paths = _listdir(path, *args, **kwargs) if isinstance(path, str): # Undecodable filenames will still be string objects. Ignore them. paths = [path for path in paths if isinstance(path, str)] @@ -465,9 +465,9 @@ def listdir_re(path, rex=None): def make_win32_compatible_long_path(path, maxpath=259): if ( sys.platform == "win32" - and len(path) > maxpath + and len(str(path)) > maxpath and os.path.isabs(path) - and not path.startswith("\\\\?\\") + and not str(path).startswith("\\\\?\\") ): path = "\\\\?\\" + path return path diff --git a/pyproject.toml b/pyproject.toml index fed528d4..97b0a427 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,3 +1,4 @@ [build-system] -requires = ["setuptools"] +requires = ["setuptools", + 'pywin32; platform_system=="Windows"'] build-backend = "setuptools.build_meta" diff --git a/util/winmanifest.py b/util/winmanifest.py index 0e72bfc1..117aa5fc 100755 --- a/util/winmanifest.py +++ b/util/winmanifest.py @@ -990,9 +990,9 @@ def toprettyxml(self, indent=" ", newl=os.linesep, encoding="UTF-8"): xmlstr = domtree.toprettyxml(indent, newl, encoding) else: xmlstr = domtree.toprettyxml(indent, newl) - xmlstr = xmlstr.strip(os.linesep).replace( - '' % encoding, - '' % encoding, + xmlstr = xmlstr.strip(os.linesep.encode('utf-8')).replace( + ('' % encoding).encode('utf-8'), + ('' % encoding).encode('utf-8'), ) domtree.unlink() return xmlstr From db5ab398002c574e0a3237a11acdee8b58495d4d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rados=C5=82aw=20Szkodzi=C5=84ski?= Date: Sat, 13 Jan 2024 17:09:51 +0100 Subject: [PATCH 2/2] Do not add support for Python 3.12 in setup.py yet, correct version handling The support for 3.12 has not been validated yet, especially on Windows. Fix a mistake in version handling code in the same place. --- DisplayCAL/setup.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/DisplayCAL/setup.py b/DisplayCAL/setup.py index 06ac0205..5fbe5c26 100644 --- a/DisplayCAL/setup.py +++ b/DisplayCAL/setup.py @@ -197,9 +197,9 @@ def add_lib_excludes(key, excludebits): config["excludes"][key].extend([f"{name}.lib{exclude}", f"lib{exclude}"]) for exclude in ("32", "64"): - for pycompat in ("38", "39", "310", "311", "312"): + for pycompat in ("38", "39", "310", "311"): if key == "win32" and ( - pycompat == str(sys.version_info[0]) + str(sys.version_info[2]) or exclude == excludebits[0] + pycompat == str(sys.version_info[0]) + str(sys.version_info[1]) or exclude == excludebits[0] ): continue config["excludes"][key].extend( @@ -955,7 +955,7 @@ def findall( packages = [name, f"{name}.lib", f"{name}.lib.agw"] if sdist: # For source distributions we want all libraries - for pycompat in ("38", "39", "310", "311", "312"): + for pycompat in ("38", "39", "310", "311"): packages.extend( [ f"{name}.lib{bits}",