Repository: marcelotduarte/cx_Freeze (language: python)

Issue:
Windows console-based application crashes immediately after opening
**Describe the bug**
I used the same python environment to freeze my program in Windows. The program runs well if running from source under the same python environment, but after freezing it, opening the executable will crash immediately.

**Expected behavior**
My application is based on `tkinter` and `base=console` in `setup.py`, as I still need to see my helpful debugging messages. Previously, this setting worked fine - there was still GUI popping up. But now, it will open a console and quickly close itself without any debugging print messages in the console, which makes diagnosing the problem very difficult.


**Desktop (please complete the following information):**
 - Platform information: WIndows11
 - OS architecture: x86
 - cx_Freeze version [e.g. 6.11]: 7.3.0.dev4
 - Python version: 3.10

**Additional context**
setup.py:
```python3
from cx_Freeze import setup, Executable

build_exe_options = {
    "build_exe": "dist",
    "includes": [
        "caiman",
    ],
    # "packages": [
    # ],
    "include_files": [
        "config.json",
        "resource",
        "log"
    ],

}
build_msi_options = {}
build_mac_options = {}
build_appimage_options = {}

executables = [
    Executable(
        "main.py",
        base="console",
        # icon="icon.ico",
        target_name="Program",
        shortcut_name="Program",
        shortcut_dir="Program",
    )
]

setup(
    name="Program",
    version="0.5",
    description="Toolbox",
    options={
        "build_exe": build_exe_options,
        "bdist_msi": build_msi_options,
        "bdist_mac": build_mac_options,
        "bdist_appimage": build_appimage_options,
    },
    executables=executables,
)
```



Discussion / hints:
I tested using a modified version of the [tkinter sample](https://github.com/marcelotduarte/cx_Freeze/tree/main/samples/tkinter) with [base="console"](https://github.com/marcelotduarte/cx_Freeze/blob/main/samples/tkinter/setup.py#L13) and a print(text) after [line 11](https://github.com/marcelotduarte/cx_Freeze/blob/main/samples/tkinter/test_tkinter.py#L12), and it worked, with the latest cx_Freeze 7.3.0.dev9
Hmm I am still seeing crashes on my side, though I didn't run on a test sample.
I got some warning messages this time from an older commit (seconds later it crashes):
![QQ_1721980386379](https://github.com/user-attachments/assets/c5c4d3cc-1434-4411-abfd-4f45bba5b853)

I am also getting error code 2756 during msi installation.

Could you try my code base to see if there are any problems? It is [here](https://github.com/CanYing0913/CalciumZero/tree/v0.5). Many thanks.
The error is related to tzdata/zoneinfo. Please check your `conda list` (mamba list?) if tzdata is installed.

Also you can use the [sample tz](https://github.com/marcelotduarte/cx_Freeze/tree/main/samples/tz) to test if it is working.
For me is it working.

I tried to clone your project, but I cannot run it with 'python main.py'. I got an error:
FileNotFoundError: [Errno 2] No such file or directory: '../data/scale_cluster_average.txt'
Thanks for providing the sample! I tried on it and it seems that one will also crash soon after starting it. Here is my list of tzdata in my mamba env:
```
mamba list tzdata
# packages in environment at C:\Users\PC\mambaforge\envs\myenv:
#
# Name                    Version                   Build  Channel
python-tzdata            2024.1             pyhd8ed1ab_0    conda-forge
tzdata                         2024a                h0c530f3_0    conda-forge
```
Are those versions supported?
I'm using the default installed, tzdata, which is the same version as yours, and the sample works.
I do not have python-tzdata. Could you remove it and make a new test with the sample?
Just created a new environment and tried on it with command `pip install -U cx_Freeze "backports.zoneinfo;python_version<'3.9'" "tzdata;sys_platform=='win32'"` and it still fails. However, I noticed there are a couple of warnings at the beginning. Are those the cause of the issue?
```
python setup.py build
running build
running build_exe
creating directory D:\CanYing\Code\cx_Freeze\samples\tz\build\exe.win-amd64-3.8
copying C:\Users\PC\mambaforge\envs\test\python38.dll -> D:\CanYing\Code\cx_Freeze\samples\tz\build\exe.win-amd64-3.8\python38.dll
copying C:\Users\PC\mambaforge\envs\test\zlib.dll -> D:\CanYing\Code\cx_Freeze\samples\tz\build\exe.win-amd64-3.8\zlib.dll
WARNING: cannot find 'api-ms-win-core-rtlsupport-l1-2-2.dll'
WARNING: cannot find 'api-ms-win-core-processthreads-l1-1-3.dll'
WARNING: cannot find 'api-ms-win-core-processthreads-l1-1-2.dll'
WARNING: cannot find 'api-ms-win-core-registry-l1-1-0.dll'
WARNING: cannot find 'api-ms-win-core-heap-l2-1-0.dll'
WARNING: cannot find 'api-ms-win-core-memory-l1-1-1.dll'
WARNING: cannot find 'api-ms-win-core-memory-l1-1-2.dll'
WARNING: cannot find 'api-ms-win-core-synch-l1-2-1.dll'
WARNING: cannot find 'api-ms-win-core-file-l1-2-2.dll'
WARNING: cannot find 'api-ms-win-core-file-l1-2-4.dll'
WARNING: cannot find 'api-ms-win-core-file-l1-2-1.dll'
WARNING: cannot find 'api-ms-win-core-delayload-l1-1-0.dll'
WARNING: cannot find 'api-ms-win-core-io-l1-1-0.dll'
WARNING: cannot find 'api-ms-win-core-io-l1-1-1.dll'
WARNING: cannot find 'api-ms-win-core-job-l1-1-0.dll'
WARNING: cannot find 'api-ms-win-core-threadpool-legacy-l1-1-0.dll'
WARNING: cannot find 'api-ms-win-core-threadpool-private-l1-1-0.dll'
WARNING: cannot find 'api-ms-win-core-largeinteger-l1-1-0.dll'
WARNING: cannot find 'api-ms-win-core-libraryloader-l1-2-3.dll'
WARNING: cannot find 'api-ms-win-core-libraryloader-l1-2-2.dll'
WARNING: cannot find 'api-ms-win-core-libraryloader-l1-2-0.dll'
WARNING: cannot find 'api-ms-win-core-libraryloader-l1-2-1.dll'
WARNING: cannot find 'api-ms-win-core-libraryloader-l2-1-0.dll'
WARNING: cannot find 'api-ms-win-core-namedpipe-l1-2-2.dll'
WARNING: cannot find 'api-ms-win-core-namedpipe-l1-2-1.dll'
WARNING: cannot find 'api-ms-win-core-datetime-l1-1-1.dll'
WARNING: cannot find 'api-ms-win-core-datetime-l1-1-2.dll'
WARNING: cannot find 'api-ms-win-core-sysinfo-l1-2-0.dll'
WARNING: cannot find 'api-ms-win-core-sysinfo-l1-2-1.dll'
WARNING: cannot find 'api-ms-win-core-sysinfo-l1-2-3.dll'
WARNING: cannot find 'api-ms-win-core-processsnapshot-l1-1-0.dll'
WARNING: cannot find 'api-ms-win-core-processenvironment-l1-2-0.dll'
WARNING: cannot find 'api-ms-win-core-debug-l1-1-1.dll'
WARNING: cannot find 'api-ms-win-core-errorhandling-l1-1-3.dll'
WARNING: cannot find 'api-ms-win-security-base-l1-1-0.dll'
WARNING: cannot find 'api-ms-win-security-base-l1-2-0.dll'
WARNING: cannot find 'api-ms-win-security-appcontainer-l1-1-0.dll'
WARNING: cannot find 'api-ms-win-core-comm-l1-1-0.dll'
WARNING: cannot find 'api-ms-win-core-realtime-l1-1-0.dll'
WARNING: cannot find 'api-ms-win-core-wow64-l1-1-1.dll'
WARNING: cannot find 'api-ms-win-core-wow64-l1-1-0.dll'
WARNING: cannot find 'api-ms-win-core-wow64-l1-1-3.dll'
WARNING: cannot find 'api-ms-win-core-systemtopology-l1-1-1.dll'
WARNING: cannot find 'api-ms-win-core-systemtopology-l1-1-0.dll'
WARNING: cannot find 'api-ms-win-core-processtopology-l1-1-0.dll'
WARNING: cannot find 'api-ms-win-core-namespace-l1-1-0.dll'
WARNING: cannot find 'api-ms-win-core-file-l2-1-2.dll'
WARNING: cannot find 'api-ms-win-core-file-l2-1-1.dll'
WARNING: cannot find 'api-ms-win-core-file-l2-1-3.dll'
WARNING: cannot find 'api-ms-win-core-xstate-l2-1-0.dll'
WARNING: cannot find 'api-ms-win-core-xstate-l2-1-1.dll'
WARNING: cannot find 'api-ms-win-core-xstate-l2-1-2.dll'
WARNING: cannot find 'api-ms-win-core-localization-l2-1-0.dll'
WARNING: cannot find 'api-ms-win-core-normalization-l1-1-0.dll'
WARNING: cannot find 'api-ms-win-core-heap-obsolete-l1-1-0.dll'
WARNING: cannot find 'api-ms-win-core-fibers-l2-1-0.dll'
WARNING: cannot find 'api-ms-win-core-fibers-l2-1-1.dll'
WARNING: cannot find 'api-ms-win-core-localization-private-l1-1-0.dll'
WARNING: cannot find 'api-ms-win-core-sidebyside-l1-1-0.dll'
WARNING: cannot find 'api-ms-win-core-appcompat-l1-1-0.dll'
WARNING: cannot find 'api-ms-win-core-windowserrorreporting-l1-1-1.dll'
WARNING: cannot find 'api-ms-win-core-windowserrorreporting-l1-1-2.dll'
WARNING: cannot find 'api-ms-win-core-windowserrorreporting-l1-1-0.dll'
WARNING: cannot find 'api-ms-win-core-windowserrorreporting-l1-1-3.dll'
WARNING: cannot find 'api-ms-win-core-console-l1-2-1.dll'
WARNING: cannot find 'api-ms-win-core-console-l2-1-0.dll'
WARNING: cannot find 'api-ms-win-core-console-l2-2-0.dll'
WARNING: cannot find 'api-ms-win-core-console-l3-2-0.dll'
WARNING: cannot find 'api-ms-win-core-psapi-l1-1-0.dll'
WARNING: cannot find 'api-ms-win-core-psapi-ansi-l1-1-0.dll'
WARNING: cannot find 'api-ms-win-core-apiquery-l1-1-0.dll'
WARNING: cannot find 'api-ms-win-core-delayload-l1-1-1.dll'
WARNING: cannot find 'api-ms-win-core-appcompat-l1-1-1.dll'
WARNING: cannot find 'ext-ms-win-oobe-query-l1-1-0.dll'
WARNING: cannot find 'ext-ms-win-packagevirtualizationcontext-l1-1-0.dll'
copying C:\Users\PC\mambaforge\envs\test\python3.dll -> D:\CanYing\Code\cx_Freeze\samples\tz\build\exe.win-amd64-3.8\python3.dll
copying C:\Users\PC\mambaforge\envs\test\Lib\site-packages\cx_Freeze\bases\console-cpython-38-win_amd64.exe -> D:\CanYing\Code\cx_Freeze\samples\tz\build\exe.win-amd64-3.8\test_tz.exe
copying C:\Users\PC\mambaforge\envs\test\Lib\site-packages\cx_Freeze\initscripts\frozen_application_license.txt -> D:\CanYing\Code\cx_Freeze\samples\tz\build\exe.win-amd64-3.8\frozen_application_license.txt
Stamped: D:\CanYing\Code\cx_Freeze\samples\tz\build\exe.win-amd64-3.8\test_tz.exe
writing zip file D:\CanYing\Code\cx_Freeze\samples\tz\build\exe.win-amd64-3.8\lib\library.zip
.......
```
I tested with python-tzdata installed and get an error on stderr. Removed it and works.
The warnings in the build about 'api-ms-win-*' are ok. To include the files please check the [FAQ](https://cx-freeze.readthedocs.io/en/stable/faq.html#microsoft-visual-c-redistributable-package).

Uninstalling python-tzdata removes the message and your app works?

It didn't work unfortunately. I didn't remove `python-tzdata` because mamba will remove all my packages that way, which is strange. In my newly created test environment, I only have `tzdata` installed:
```
mamba list tzdata
mamba list tzdata
# packages in environment at C:\Users\PC\mambaforge\envs\test:
#
# Name                    Version                   Build  Channel
tzdata                    2024.1                   pypi_0    pypi
```
But it still does not work.
P.S. My code in my repo is on branch `v0.5` instead of `master` branch. Sorry for the confusion
> P.S. My code in my repo is on branch `v0.5` instead of `master` branch. Sorry for the confusion

I tried it, and it does not have 'data/scale_cluster_average.txt'. I cannot test.
Oops sorry about that. I forgot to comment out some incomplete work. Can you try now?
To create the environment, I used this command:
```
mamba create -n test_env -c conda-forge -c anaconda python caiman pyimagej openjdk=8
pip install --force --no-cache --pre --extra-index-url https://marcelotduarte.github.io/packages/ cx_Freeze
```
tzdata will be a dependent library from those above.
@marcelotduarte I have fixed the issue. However, I think the cause of issue is worth noting. I replaced all `match... case...` clauses with ifs and elifs and that solved it. It is weird because the python environment I used to freeze my program supports match/case statement (`Python>=3.9`).

Fix patch:
diff --git a/cx_Freeze/freezer.py b/cx_Freeze/freezer.py
index 2f8f67d83..f6f170979 100644
--- a/cx_Freeze/freezer.py
+++ b/cx_Freeze/freezer.py
@@ -191,9 +191,15 @@ def _copy_file(
         if self.silent < 1:
             print(f"copying {source} -> {target}")
         shutil.copyfile(source, target)
-        shutil.copystat(source, target)
         if include_mode:
             shutil.copymode(source, target)
+            shutil.copystat(source, target)
+        else:
+            try:
+                shutil.copystat(source, target)
+            except OSError:
+                if self.silent < 3:
+                    print("WARNING: unable to copy file metadata:", target)
         self.files_copied.add(target)
 
         # handle post-copy tasks, including copying dependencies
diff --git a/cx_Freeze/hooks/__init__.py b/cx_Freeze/hooks/__init__.py
index 312ee8844..5728de7b6 100644
--- a/cx_Freeze/hooks/__init__.py
+++ b/cx_Freeze/hooks/__init__.py
@@ -653,12 +653,6 @@ def load_twitter(finder: ModuleFinder, module: Module) -> None:
     module.ignore_names.update(["json", "simplejson", "django.utils"])
 
 
-def load_tzdata(finder: ModuleFinder, module: Module) -> None:
-    """The tzdata package requires its zone and timezone data."""
-    if module.in_file_system == 0:
-        finder.zip_include_files(module.file.parent, "tzdata")
-
-
 def load_uvloop(finder: ModuleFinder, module: Module) -> None:
     """The uvloop module implicitly loads an extension module."""
     finder.include_module("uvloop._noop")
diff --git a/cx_Freeze/hooks/tzdata.py b/cx_Freeze/hooks/tzdata.py
new file mode 100644
index 000000000..ad9c6bae8
--- /dev/null
+++ b/cx_Freeze/hooks/tzdata.py
@@ -0,0 +1,17 @@
+"""A collection of functions which are triggered automatically by finder when
+tzdata package is included.
+"""
+
+from __future__ import annotations
+
+from typing import TYPE_CHECKING
+
+if TYPE_CHECKING:
+    from cx_Freeze.finder import ModuleFinder
+    from cx_Freeze.module import Module
+
+
+def load_tzdata(finder: ModuleFinder, module: Module) -> None:
+    """The tzdata package requires its zone and timezone data."""
+    if module.in_file_system == 0:
+        finder.zip_include_files(module.file.parent, "tzdata")
diff --git a/cx_Freeze/hooks/zoneinfo.py b/cx_Freeze/hooks/zoneinfo.py
index db55fddc9..b67a1d4c1 100644
--- a/cx_Freeze/hooks/zoneinfo.py
+++ b/cx_Freeze/hooks/zoneinfo.py
@@ -5,6 +5,7 @@
 from __future__ import annotations
 
 from pathlib import Path
+from textwrap import dedent
 from typing import TYPE_CHECKING
 
 if TYPE_CHECKING:
@@ -16,31 +17,62 @@ def load_zoneinfo(finder: ModuleFinder, module: Module) -> None:
     """The zoneinfo package requires timezone data,
     that can be the in tzdata package, if installed.
     """
+    module.global_names.add("TZPATH")
     try:
         finder.include_package("tzdata")
+        target_path = "lib/tzdata/zoneinfo"
     except ImportError:
-        pass
-    else:
-        return
+        target_path = None
 
-    # without tzdata, copy only zoneinfo directory
-    source_path = None
-    zoneinfo = __import__(module.name, fromlist=["TZPATH"])
-    if zoneinfo.TZPATH:
-        for path in zoneinfo.TZPATH:
-            if path.endswith("zoneinfo"):
-                source_path = Path(path)
-                break
-    if source_path is None or not source_path.is_dir():
-        return
-    if module.in_file_system == 0:
-        finder.zip_include_files(source_path, "tzdata/zoneinfo")
-    else:
+    if target_path is None:
+        # without tzdata, copy zoneinfo directory if available
+        source_path = None
+        zoneinfo = __import__(module.name, fromlist=["TZPATH"])
+        if zoneinfo.TZPATH:
+            for path in zoneinfo.TZPATH:
+                if path.endswith("zoneinfo"):
+                    source_path = Path(path).resolve()
+                    break
+        if source_path is None or not source_path.is_dir():
+            # add tzdata to missing modules
+            bad_modules = finder._bad_modules  # noqa: SLF001
+            callers = bad_modules.setdefault("tzdata", {})
+            callers[f"{module.name}_hook"] = None
+            return
+        if module.in_file_system == 0:
+            finder.zip_include_files(source_path, "tzdata/zoneinfo")
+            return
         target_path = "share/zoneinfo"
-        finder.add_constant("PYTHONTZPATH", target_path)
         finder.include_files(
             source_path, target_path, copy_dependent_files=False
         )
 
+    # patch source code
+    if module.file.suffix == ".pyc":  # source unavailable
+        return
+
+    source = f"""
+        # cx_Freeze patch start
+        import os as _os
+        import sys as _sys
+        _prefix = _sys.prefix if _sys.prefix else _sys.frozen_dir
+        if _sys.platform == "darwin":
+            _mac_prefix = _os.path.join(_os.path.dirname(_prefix), "Resources")
+            if _os.path.exists(_mac_prefix):
+                _prefix = _mac_prefix  # using bdist_mac
+        _os.environ["PYTHONTZPATH"] = _os.path.join(
+            _prefix, _os.path.normpath("{target_path}")
+        )
+        # cx_Freeze patch end
+    """
+    code_string = module.file.read_text(encoding="utf_8")
+    module.code = compile(
+        dedent(source) + code_string,
+        module.file.as_posix(),
+        "exec",
+        dont_inherit=True,
+        optimize=finder.optimize,
+    )
+
 
 __all__ = ["load_zoneinfo"]
diff --git a/cx_Freeze/initscripts/__startup__.py b/cx_Freeze/initscripts/__startup__.py
index 069a191fa..595c49c81 100644
--- a/cx_Freeze/initscripts/__startup__.py
+++ b/cx_Freeze/initscripts/__startup__.py
@@ -101,7 +101,6 @@ def init() -> None:
     for name in (
         "TCL_LIBRARY",
         "TK_LIBRARY",
-        "PYTZ_TZDATADIR",
         "PYTHONTZPATH",
     ):
         try:


Test patch:
diff --git a/samples/tz/test_tz.py b/samples/tz/test_tz.py
index 60068bc75..e2381ebbe 100644
--- a/samples/tz/test_tz.py
+++ b/samples/tz/test_tz.py
@@ -8,12 +8,16 @@
 from datetime import datetime, timezone
 
 try:
-    from zoneinfo import ZoneInfo
+    from zoneinfo import TZPATH, ZoneInfo, available_timezones
 except ImportError:
-    from backports.zoneinfo import ZoneInfo
+    from backports.zoneinfo import TZPATH, ZoneInfo, available_timezones
+
 
 RFC1123 = "%a, %d %b %Y %H:%M:%S %z"
 
+print("TZPATH:", TZPATH)
+print("Available timezones:", len(available_timezones()))
+
 utc_time = datetime.now(timezone.utc)
 print("UTC time:", utc_time.strftime(RFC1123))
 
diff --git a/tests/test_hooks_zoneinfo.py b/tests/test_hooks_zoneinfo.py
new file mode 100644
index 000000000..234d2b3ca
--- /dev/null
+++ b/tests/test_hooks_zoneinfo.py
@@ -0,0 +1,66 @@
+"""Tests for cx_Freeze.hooks.zoneinfo."""
+
+from __future__ import annotations
+
+import sys
+from typing import TYPE_CHECKING
+
+import pytest
+from generate_samples import create_package, run_command
+
+from cx_Freeze._compat import BUILD_EXE_DIR, EXE_SUFFIX
+
+if TYPE_CHECKING:
+    from pathlib import Path
+
+
+SOURCE = """
+test_tz.py
+    from datetime import datetime, timezone
+    from zoneinfo import TZPATH, ZoneInfo, available_timezones
+
+    RFC1123 = "%a, %d %b %Y %H:%M:%S %z"
+
+    print("TZPATH:", TZPATH)
+    print("Available timezones:", len(available_timezones()))
+
+    utc_time = datetime.now(timezone.utc)
+    print("UTC time:", utc_time.strftime(RFC1123))
+
+    tz1 = ZoneInfo("America/Sao_Paulo")
+    brz_time = utc_time.astimezone(tz1)
+    print("Brazil time:", brz_time.strftime(RFC1123))
+
+    tz2 = ZoneInfo("US/Eastern")
+    eas_time = utc_time.astimezone(tz2)
+    print("US Eastern time:", eas_time.strftime(RFC1123))
+command
+    cxfreeze --script test_tz.py build_exe --excludes=tkinter --silent
+"""
+
+
+@pytest.mark.skipif(
+    sys.version_info < (3, 9), reason="Python < 3.9 doesn't provide zoneinfo"
+)
+@pytest.mark.parametrize(
+    "zip_packages", [False, True], ids=["", "zip_packages"]
+)
+def test_tz(tmp_path: Path, zip_packages: bool) -> None:
+    """Test if zoneinfo hook is working correctly."""
+    create_package(tmp_path, SOURCE)
+    if zip_packages:
+        with tmp_path.joinpath("command").open("a") as f:
+            f.write(" --zip-include-packages=* --zip-include-packages=")
+    output = run_command(tmp_path)
+    if "? tzdata imported from zoneinfo_hook" in output:
+        pytest.skip(reason="tzdata must be installed")
+
+    executable = tmp_path / BUILD_EXE_DIR / f"test_tz{EXE_SUFFIX}"
+    assert executable.is_file()
+    output = run_command(tmp_path, executable, timeout=10)
+    lines = output.splitlines()
+    assert lines[0].startswith("TZPATH")
+    assert lines[1].startswith("Available")
+    assert lines[2].startswith("UTC")
+    assert lines[3].startswith("Brazil")
+    assert lines[4].startswith("US")