When trying to freeze a script which imports any distutils
module (for example, distutils.dist
), the script fails at runtime with an exception like this:
> from distutils import dist, sysconfig
E ImportError: cannot import name dist
(In my case, the error was raised by matplotlib
, which tries to import distutils.dist
).
The problem is easy to reproduce:
$ virtualenv myenv
$ source myenv/bin/activate
$ pip install cx_freeze
# contents of setup.py
from cx_Freeze import setup, Executable
build_exe_options = {}
setup(
name="foo",
version="0.1",
description="My app",
options={"build_exe": build_exe_options},
executables=[Executable("foo_main.py", base=None)],
)
# contents of foo_main.py
import distutils.dist
$ python setup.py build
Running the produced executable should raise the same error (tested with cx-freeze 4.3.4).
This problem happens because distutils
does not install all its modules into the virtualenv, it only creates a package with some magic code in the __init__
file to import its submodules dynamically. This is a problem to cx-freeze's static module analysis, which complains during the build
command that it can't find distutils modules.
The workaround used was to tell cx-freeze to exclude distutils
and add the package from the original interpreter (not from the virtualenv) manually.
# contents of setup.py
from cx_Freeze import setup, Executable
import distutils
import opcode
import os
# opcode is not a virtualenv module, so we can use it to find the stdlib; this is the same
# trick used by distutils itself it installs itself into the virtualenv
distutils_path = os.path.join(os.path.dirname(opcode.__file__), 'distutils')
build_exe_options = {'include_files': [(distutils_path, 'distutils')], "excludes": ["distutils"]}
setup(
name="foo",
version="0.1",
description="My app",
options={"build_exe": build_exe_options},
executables=[Executable("foo_main.py", base=None)],
)
@nicoddemus && @bobatsar:
thanks for the hint. That helped me alot +1
for completeness' sake..
This is the setup.py I used:
import sys
from cx_Freeze import setup, Executable
#stuff to use distutils from outside the virtual environment
import distutils
import opcode
import os
distutils_path = os.path.join(os.path.dirname(opcode.file), 'distutils')
#end
base = None
if sys.platform == 'win32':
base = 'Win32GUI'
#with the tag 'include_files' we tell cx_freeze to include the distutils from outside our python environment
#with the tag 'exclude' we explicitly exclude the distutils inside our python environment
options = {
'build_exe': {
'includes': 'atexit',
'include_files': [(distutils_path, 'lib/distutils')],
'packages': ['pkg_resources._vendor.packaging', 'idna', 'dumbdbm', 'ftrack_action_handler'],
'excludes': ['distutils']
}
}
executables = [
Executable('main.py', base=base)
]
setup(name='ftrack-connect',
version='0.1',
description='ftrack-connect client to communicate with FTrack-Server',
options=options,
executables=executables
)