hyb
2025-12-23 c980682a1fe205d8c21d349e9fc6b9e4951aea34
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
# ------------------------------------------------------------------
# Copyright (c) 2020 PyInstaller Development Team.
#
# This file is distributed under the terms of the GNU General Public
# License (version 2.0 or later).
#
# The full license is available in LICENSE, distributed with
# this software.
#
# SPDX-License-Identifier: GPL-2.0-or-later
# ------------------------------------------------------------------
 
from _pyinstaller_hooks_contrib.compat import importlib_metadata
from packaging.version import Version
 
from PyInstaller.compat import is_linux
from PyInstaller.utils.hooks import (
    collect_data_files,
    collect_dynamic_libs,
    collect_submodules,
    get_module_attribute,
    is_module_satisfies,
    logger,
)
 
# Determine the name of `tensorflow` dist; this is available under different names (releases vs. nightly, plus build
# variants). We need to determine the dist that we are dealing with, so we can query its version and metadata.
_CANDIDATE_DIST_NAMES = (
    "tensorflow",
    "tensorflow-cpu",
    "tensorflow-gpu",
    "tensorflow-intel",
    "tensorflow-rocm",
    "tensorflow-macos",
    "tensorflow-aarch64",
    "tensorflow-cpu-aws",
    "tf-nightly",
    "tf-nightly-cpu",
    "tf-nightly-gpu",
    "tf-nightly-rocm",
    "intel-tensorflow",
    "intel-tensorflow-avx512",
)
dist = None
for candidate_dist_name in _CANDIDATE_DIST_NAMES:
    try:
        dist = importlib_metadata.distribution(candidate_dist_name)
        break
    except importlib_metadata.PackageNotFoundError:
        continue
 
version = None
if dist is None:
    logger.warning(
        "hook-tensorflow: failed to determine tensorflow dist name! Reading version from tensorflow.__version__!"
    )
    try:
        version = get_module_attribute("tensorflow", "__version__")
    except Exception as e:
        raise Exception("Failed to read tensorflow.__version__") from e
else:
    logger.info("hook-tensorflow: tensorflow dist name: %s", dist.name)
    version = dist.version
 
# Parse version
logger.info("hook-tensorflow: tensorflow version: %s", version)
try:
    version = Version(version)
except Exception as e:
    raise Exception("Failed to parse tensorflow version!") from e
 
# Exclude from data collection:
#  - development headers in include subdirectory
#  - XLA AOT runtime sources
#  - libtensorflow_framework and libtensorflow_cc (since TF 2.12) shared libraries (to avoid duplication)
#  - import library (.lib) files (Windows-only)
data_excludes = [
    "include",
    "xla_aot_runtime_src",
    "libtensorflow_framework.*",
    "libtensorflow_cc.*",
    "**/*.lib",
]
 
# Under tensorflow 2.3.0 (the most recent version at the time of writing), _pywrap_tensorflow_internal extension module
# ends up duplicated; once as an extension, and once as a shared library. In addition to increasing program size, this
# also causes problems on macOS, so we try to prevent the extension module "variant" from being picked up.
#
# See pyinstaller/pyinstaller-hooks-contrib#49 for details.
#
# With PyInstaller >= 6.0, this issue is alleviated, because the binary dependency analysis (which picks up the
# extension in question as a shared library that other extensions are linked against) now preserves the parent directory
# layout, and creates a symbolic link to the top-level application directory.
if is_module_satisfies('PyInstaller >= 6.0'):
    excluded_submodules = []
else:
    excluded_submodules = ['tensorflow.python._pywrap_tensorflow_internal']
 
 
def _submodules_filter(x):
    return x not in excluded_submodules
 
 
if version < Version("1.15.0a0"):
    # 1.14.x and earlier: collect everything from tensorflow
    hiddenimports = collect_submodules('tensorflow', filter=_submodules_filter)
    datas = collect_data_files('tensorflow', excludes=data_excludes)
elif version >= Version("1.15.0a0") and version < Version("2.2.0a0"):
    # 1.15.x - 2.1.x: collect everything from tensorflow_core
    hiddenimports = collect_submodules('tensorflow_core', filter=_submodules_filter)
    datas = collect_data_files('tensorflow_core', excludes=data_excludes)
 
    # Under 1.15.x, we seem to fail collecting a specific submodule, and need to add it manually...
    if version < Version("2.0.0a0"):
        hiddenimports += ['tensorflow_core._api.v1.compat.v2.summary.experimental']
else:
    # 2.2.0 and newer: collect everything from tensorflow again
    hiddenimports = collect_submodules('tensorflow', filter=_submodules_filter)
    datas = collect_data_files('tensorflow', excludes=data_excludes)
 
    # From 2.6.0 on, we also need to explicitly collect keras (due to lazy mapping of tensorflow.keras.xyz -> keras.xyz)
    if version >= Version("2.6.0a0"):
        hiddenimports += collect_submodules('keras')
 
    # Starting with 2.14.0, we need `ml_dtypes` among hidden imports.
    if version >= Version("2.14.0"):
        hiddenimports += ['ml_dtypes']
 
binaries = []
excludedimports = excluded_submodules
 
# Suppress warnings for missing hidden imports generated by this hook.
# Requires PyInstaller > 5.1 (with pyinstaller/pyinstaller#6914 merged); no-op otherwise.
warn_on_missing_hiddenimports = False
 
# Collect the AutoGraph part of `tensorflow` code, to avoid a run-time warning about AutoGraph being unavailable:
# `WARNING:tensorflow:AutoGraph is not available in this environment: functions lack code information. ...`
# The warning is emitted if source for `log` function from `tensorflow.python.autograph.utils.ag_logging` cannot be
# looked up. Not sure if we need sources for other parts of `tesnorflow`, though.
# Requires PyInstaller >= 5.3, no-op in older versions.
module_collection_mode = {
    'tensorflow.python.autograph': 'py+pyz',
}
 
# Linux builds of tensorflow can optionally use CUDA from nvidia-* packages. If we managed to obtain dist, query the
# requirements from metadata (the `and-cuda` extra marker), and convert them to module names.
#
# NOTE: while the installation of nvidia-* packages via `and-cuda` extra marker is not gated by the OS version check,
# it is effectively available only on Linux (last Windows-native build that supported GPU is v2.10.0, and assumed that
# CUDA is externally available).
if is_linux and dist is not None:
    def _infer_nvidia_hiddenimports():
        import packaging.requirements
        from _pyinstaller_hooks_contrib.utils import nvidia_cuda as cudautils
 
        requirements = [packaging.requirements.Requirement(req) for req in dist.requires or []]
        env = {'extra': 'and-cuda'}
        requirements = [req.name for req in requirements if req.marker is None or req.marker.evaluate(env)]
 
        return cudautils.infer_hiddenimports_from_requirements(requirements)
 
    try:
        nvidia_hiddenimports = _infer_nvidia_hiddenimports()
    except Exception:
        # Log the exception, but make it non-fatal
        logger.warning("hook-tensorflow: failed to infer NVIDIA CUDA hidden imports!", exc_info=True)
        nvidia_hiddenimports = []
    logger.info("hook-tensorflow: inferred hidden imports for CUDA libraries: %r", nvidia_hiddenimports)
    hiddenimports += nvidia_hiddenimports
 
 
# Collect the tensorflow-plugins (pluggable device plugins)
hiddenimports += ['tensorflow-plugins']
binaries += collect_dynamic_libs('tensorflow-plugins')
 
# On Linux, prevent binary dependency analysis from generating symbolic links for libtensorflow_cc.so.2,
# libtensorflow_framework.so.2, and _pywrap_tensorflow_internal.so to the top-level application directory. These
# symbolic links seem to confuse tensorflow about its location (likely because code in one of the libraries looks up the
# library file's location, but does not fully resolve it), which in turn prevents it from finding the collected CUDA
# libraries in the nvidia/cu* package directories.
#
# The `bindepend_symlink_suppression` hook attribute requires PyInstaller >= 6.11, and is no-op in earlier versions.
if is_linux:
    bindepend_symlink_suppression = [
        '**/libtensorflow_cc.so*',
        '**/libtensorflow_framework.so*',
        '**/_pywrap_tensorflow_internal.so',
    ]