Skip to content

Instantly share code, notes, and snippets.

@nicoddemus
Created May 19, 2015 17:45
Show Gist options
  • Save nicoddemus/ca0acd93a20acbc42d1d to your computer and use it in GitHub Desktop.
Save nicoddemus/ca0acd93a20acbc42d1d to your computer and use it in GitHub Desktop.
Problem trying to freeze an executable which imports distutils modules (directly or indirectly)

cxFreeze and distutils

Problem

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.

Workaround

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)],
)
@julien-duponchelle
Copy link

THANKS A LOT !

@onsmribah
Copy link

Thanks, this was very helpful !

@frenchbeast
Copy link

Thanks!! That helped as well

@bobatsar
Copy link

bobatsar commented Aug 30, 2017

Just wanted to note that with cx_freeze 5.0.2 I had to change the destination path to 'lib/distutils' on windows. But then it worked again.
For OSX it has to stay at "distutils"

@bobatsar
Copy link

Sorry, my last comment was wrong. It has to be "lib/distutils" on both platforms.

@tim-edelmann
Copy link

tim-edelmann commented Nov 29, 2017

@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
)

@Kamforka
Copy link

Thanks for this beautiful workaround! Saved me days of debugging.

@marcelotduarte
Copy link

cx_Freeze 6 has been release. The workaround is no longer needed either.

@jeffrothkirch
Copy link

Confirming that I have cx_freexe 6, and I still needed this code! Thanks!

@NeroVanbiervliet
Copy link

NeroVanbiervliet commented Jan 21, 2020

Same here! (version 6.2)

@VictorShima
Copy link

[email protected] with python3.6 in a virtualenv still required the workaround.

@marcelotduarte
Copy link

marcelotduarte commented Feb 23, 2020

@jeffrothkirch @NeroVanbiervliet @VictoryShima
You can take a look in my comment at: marcelotduarte/cx_Freeze#443
And suggest any modification to sample to test it. I'll try to solve this for 6.2 release.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment