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)],
)
cx_Freeze 6 has been release. The workaround is no longer needed either.