Repository: marcelotduarte/cx_Freeze (python)

Issue:
Cannot freeze python-3.12 code on Windows 11
**Describe the bug**
Cannot freeze python 3.12 code on Windows 11 Pro amd64 using cx_Freeze 6.16.aplha versions, last I tried is 20.
This was working fine three weeks ago, but suddenly it started to fail like this:
```
copying C:\Users\jmarcet\scoop\apps\openjdk17\17.0.2-8\bin\api-ms-win-core-console-l1-2-0.dll -> C:\Users\jmarcet\src\movistar-u7d\build\exe.win-amd64-3.12\api-ms-win-core-console-l1-2-0.dll
copying C:\Users\jmarcet\scoop\apps\python312\3.12.1\python312.dll -> C:\Users\jmarcet\src\movistar-u7d\build\exe.win-amd64-3.12\python312.dll
WARNING: cannot find 'api-ms-win-core-path-l1-1-0.dll'
copying C:\Users\jmarcet\scoop\persist\python312\Lib\site-packages\cx_Freeze\bases\console-cpython-312-win_amd64.exe -> C:\Users\jmarcet\src\movistar-u7d\build\exe.win-amd64-3.12\movistar_epg.exe
copying C:\Users\jmarcet\scoop\persist\python312\Lib\site-packages\cx_Freeze\initscripts\frozen_application_license.txt -> C:\Users\jmarcet\src\movistar-u7d\build\exe.win-amd64-3.12\frozen_application_license.txt
data=72092
Traceback (most recent call last):
  File "C:\Users\jmarcet\src\movistar-u7d\setup.py", line 25, in <module>
    setup(
  File "C:\Users\jmarcet\scoop\apps\python312\current\Lib\site-packages\cx_Freeze\__init__.py", line 68, in setup
    setuptools.setup(**attrs)
  File "C:\Users\jmarcet\scoop\apps\python312\current\Lib\site-packages\setuptools\__init__.py", line 103, in setup
    return distutils.core.setup(**attrs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\jmarcet\scoop\apps\python312\current\Lib\site-packages\setuptools\_distutils\core.py", line 185, in setup
    return run_commands(dist)
           ^^^^^^^^^^^^^^^^^^
  File "C:\Users\jmarcet\scoop\apps\python312\current\Lib\site-packages\setuptools\_distutils\core.py", line 201, in run_commands
    dist.run_commands()
  File "C:\Users\jmarcet\scoop\apps\python312\current\Lib\site-packages\setuptools\_distutils\dist.py", line 969, in run_commands
    self.run_command(cmd)
  File "C:\Users\jmarcet\scoop\apps\python312\current\Lib\site-packages\setuptools\dist.py", line 963, in run_command
    super().run_command(command)
  File "C:\Users\jmarcet\scoop\apps\python312\current\Lib\site-packages\setuptools\_distutils\dist.py", line 988, in run_command
    cmd_obj.run()
  File "C:\Users\jmarcet\scoop\apps\python312\current\Lib\site-packages\setuptools\_distutils\command\build.py", line 131, in run
    self.run_command(cmd_name)
  File "C:\Users\jmarcet\scoop\apps\python312\current\Lib\site-packages\setuptools\_distutils\cmd.py", line 318, in run_command
    self.distribution.run_command(command)
  File "C:\Users\jmarcet\scoop\apps\python312\current\Lib\site-packages\setuptools\dist.py", line 963, in run_command
    super().run_command(command)
  File "C:\Users\jmarcet\scoop\apps\python312\current\Lib\site-packages\setuptools\_distutils\dist.py", line 988, in run_command
    cmd_obj.run()
  File "C:\Users\jmarcet\scoop\apps\python312\current\Lib\site-packages\cx_Freeze\command\build_exe.py", line 284, in run
    freezer.freeze()
  File "C:\Users\jmarcet\scoop\apps\python312\current\Lib\site-packages\cx_Freeze\freezer.py", line 731, in freeze
    self._freeze_executable(executable)
  File "C:\Users\jmarcet\scoop\apps\python312\current\Lib\site-packages\cx_Freeze\freezer.py", line 323, in _freeze_executable
    self._add_resources(exe)
  File "C:\Users\jmarcet\scoop\apps\python312\current\Lib\site-packages\cx_Freeze\freezer.py", line 794, in _add_resources
    version.stamp(target_path)
  File "C:\Users\jmarcet\scoop\apps\python312\current\Lib\site-packages\cx_Freeze\winversioninfo.py", line 240, in stamp
    handle, RT_VERSION, ID_VERSION, string_version_info.to_buffer()
                                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\jmarcet\scoop\apps\python312\current\Lib\site-packages\cx_Freeze\winversioninfo.py", line 96, in to_buffer
    data = pack(fmt, data)
           ^^^^^^^^^^^^^^^
struct.error: 'H' format requires 0 <= number <= 65535
```

**To Reproduce**
```
git clone -b next https://github.com/jmarcet/movistar-u7d
cd movistar-u7d
pip install --force --no-cache --pre --upgrade --extra-index-url https://marcelotduarte.github.io/packages/cx_Freeze
pip install -r requirements-win.txt
python .\setup.py build
```

**Expected behavior**
Frozen artifacts saved under `build` dir

**Desktop (please complete the following information):**
 - Platform information: Windows 11 Pro
 - OS architecture: amd64
 - cx_Freeze version: [cx_Freeze-6.16.0.dev20-cp312-cp312-win_amd64.whl](https://marcelotduarte.github.io/packages/cx-freeze/cx_Freeze-6.16.0.dev20-cp312-cp312-win_amd64.whl)
 - Python version: 3.12.1

**Additional context**
I had initially reported it on #2153


Discussion:
Please check the version installed of cx_Freeze and setuptools with `pip list`.
Successfully installed aiofiles-23.2.1 aiohttp-3.9.1 aiosignal-1.3.1 asyncio-3.4.3 asyncio_dgram-2.1.2 attrs-23.2.0 cx-Logging-3.1.0 **cx_Freeze-6.16.0.dev9** defusedxml-0.8.0rc2 filelock-3.13.1 frozenlist-1.4.1 httptools-0.6.1 idna-3.6 lief-0.14.0 multidict-6.0.4 prometheus-client-0.7.1 psutil-5.9.8 pywin32-306 sanic-22.6.2 sanic-prometheus-0.2.1 sanic-routing-22.3.0 **setuptools-68.2.2** tomli-2.0.1 ujson-5.9.0 websockets-10.4 wheel-0.41.2 wmi-1.5.1 xmltodict-0.13.0 yarl-1.9.4

You should update your requirements-win.txt, insert the first line:
--extra-index-url https://marcelotduarte.github.io/packages/

OR install the new development release after the requirements. Also, update setuptools.


@marcelotduarte I still have the same issue

```
> pip list
Package            Version
------------------ ------------
aiofiles           23.2.1
aiohttp            3.9.1
aiosignal          1.3.1
astroid            3.0.2
asttokens          2.4.1
asyncio            3.4.3
asyncio-dgram      2.1.2
attrs              23.2.0
bandit             1.7.6
certifi            2023.11.17
charset-normalizer 3.3.2
colorama           0.4.6
cx-Freeze          6.16.0.dev23
cx_Logging         3.1.0
decorator          5.1.1
defusedxml         0.7.1
dill               0.3.7
executing          2.0.1
filelock           3.13.1
frozenlist         1.4.1
gitdb              4.0.11
GitPython          3.1.41
httpie             3.2.2
httptools          0.6.1
idna               3.6
ipython            8.20.0
isort              5.13.2
jedi               0.19.1
lief               0.15.0
markdown-it-py     3.0.0
matplotlib-inline  0.1.6
mccabe             0.7.0
mdurl              0.1.2
multidict          6.0.4
parso              0.8.3
pbr                6.0.0
pip                23.2.1
platformdirs       4.1.0
prometheus-client  0.7.1
prompt-toolkit     3.0.43
psutil             5.9.8
pure-eval          0.2.2
Pygments           2.17.2
pylint             3.0.3
pynvim             0.5.0
PySocks            1.7.1
pywin32            306
PyYAML             6.0.1
requests           2.31.0
requests-toolbelt  1.0.0
rich               13.7.0
ruff               0.1.11
sanic              22.6.2
sanic-prometheus   0.2.1
sanic-routing      22.3.0
setuptools         69.0.3
six                1.16.0
smmap              5.0.1
stack-data         0.6.3
stevedore          5.1.0
tomli              2.0.1
tomlkit            0.12.3
traitlets          5.14.1
ujson              5.9.0
urllib3            2.1.0
wcwidth            0.2.13
websockets         10.4
wheel              0.42.0
WMI                1.5.1
xmltodict          0.13.0
yarl               1.9.4
```

Patch:
diff --git a/cx_Freeze/winversioninfo.py b/cx_Freeze/winversioninfo.py
--- a/cx_Freeze/winversioninfo.py
+++ b/cx_Freeze/winversioninfo.py
@@ -12,16 +12,16 @@
 
 __all__ = ["Version", "VersionInfo"]
 
+# types
+CHAR = "c"
+WCHAR = "ss"
+WORD = "=H"
+DWORD = "=L"
+
 # constants
 RT_VERSION = 16
 ID_VERSION = 1
 
-# types
-CHAR = "c"
-DWORD = "L"
-WCHAR = "H"
-WORD = "H"
-
 VS_FFI_SIGNATURE = 0xFEEF04BD
 VS_FFI_STRUCVERSION = 0x00010000
 VS_FFI_FILEFLAGSMASK = 0x0000003F
@@ -32,6 +32,8 @@
 KEY_STRING_TABLE = "040904E4"
 KEY_VAR_FILE_INFO = "VarFileInfo"
 
+COMMENTS_MAX_LEN = (64 - 2) * 1024 // calcsize(WCHAR)
+
 # To disable the experimental feature in Windows:
 # set CX_FREEZE_STAMP=pywin32
 # pip install -U pywin32
@@ -82,7 +84,7 @@ def to_buffer(self):
                 data = data.to_buffer()
             elif isinstance(data, str):
                 data = data.encode("utf-16le")
-            elif isinstance(fmt, str):
+            elif isinstance(data, int):
                 data = pack(fmt, data)
             buffer += data
         return buffer
@@ -142,7 +144,9 @@ def __init__(
             value_len = value.wLength
             fields.append(("Value", type(value)))
         elif isinstance(value, Structure):
-            value_len = calcsize("".join([f[1] for f in value._fields]))
+            value_len = 0
+            for field in value._fields:
+                value_len += calcsize(field[1])
             value_type = 0
             fields.append(("Value", type(value)))
 
@@ -199,7 +203,8 @@ def __init__(
         self.valid_version: Version = valid_version
         self.internal_name: str | None = internal_name
         self.original_filename: str | None = original_filename
-        self.comments: str | None = comments
+        # comments length must be limited to 31kb
+        self.comments: str = comments[:COMMENTS_MAX_LEN] if comments else None
         self.company: str | None = company
         self.description: str | None = description
         self.copyright: str | None = copyright
@@ -221,6 +226,8 @@ def stamp(self, path: str | Path) -> None:
                 version_stamp = import_module("win32verstamp").stamp
             except ImportError as exc:
                 raise RuntimeError("install pywin32 extension first") from exc
+            # comments length must be limited to 15kb (uses WORD='h')
+            self.comments = (self.comments or "")[: COMMENTS_MAX_LEN // 2]
             version_stamp(os.fspath(path), self)
             return
 
@@ -263,17 +270,18 @@ def version_info(self, path: Path) -> String:
         elif len(self.valid_version.release) >= 4:
             build = self.valid_version.release[3]
 
+        # use the data in the order shown in 'pepper'
         data = {
-            "Comments": self.comments or "",
-            "CompanyName": self.company or "",
             "FileDescription": self.description or "",
             "FileVersion": self.version,
             "InternalName": self.internal_name or path.name,
+            "CompanyName": self.company or "",
             "LegalCopyright": self.copyright or "",
             "LegalTrademarks": self.trademarks or "",
             "OriginalFilename": self.original_filename or path.name,
             "ProductName": self.product or "",
             "ProductVersion": str(self.valid_version),
+            "Comments": self.comments or "",
         }
         is_dll = self.dll
         if is_dll is None:
@@ -311,6 +319,7 @@ def version_info(self, path: Path) -> String:
         string_version_info = String(KEY_VERSION_INFO, fixed_file_info)
         string_version_info.children(string_file_info)
         string_version_info.children(var_file_info)
+
         return string_version_info
 
 


Test patch:
diff --git a/tests/test_winversioninfo.py b/tests/test_winversioninfo.py
--- a/tests/test_winversioninfo.py
+++ b/tests/test_winversioninfo.py
@@ -9,7 +9,12 @@
 import pytest
 from generate_samples import create_package, run_command
 
-from cx_Freeze.winversioninfo import Version, VersionInfo, main_test
+from cx_Freeze.winversioninfo import (
+    COMMENTS_MAX_LEN,
+    Version,
+    VersionInfo,
+    main_test,
+)
 
 PLATFORM = get_platform()
 PYTHON_VERSION = get_python_version()
@@ -97,6 +102,14 @@ def test___init__with_kwargs(self):
         assert version_instance.debug is input_debug
         assert version_instance.verbose is input_verbose
 
+    def test_big_comment(self):
+        """Tests a big comment value for the VersionInfo class."""
+        input_version = "9.9.9.9"
+        input_comments = "TestComment" + "=" * COMMENTS_MAX_LEN
+        version_instance = VersionInfo(input_version, comments=input_comments)
+        assert version_instance.version == "9.9.9.9"
+        assert version_instance.comments == input_comments[:COMMENTS_MAX_LEN]
+
     @pytest.mark.parametrize(
         ("input_version", "version"),
         [