Repository: spyder-ide/spyder

Issue:
Tkinter failed to import
I have installed Python 3.7.9 64bit in Windows 10 Pro and have installed the latest Spyder from setup installer for windows from [here](www.spyder-ide.org).  
When I import tkinter module, I get this error :
```
import tkinter as tk
Traceback (most recent call last):

  File "<ipython-input-2-e89bfb5dd03b>", line 1, in <module>
    import tkinter as tk

  File "C:\Program Files\Spyder\pkgs\tkinter\__init__.py", line 36, in <module>
    import _tkinter # If this fails your Python may not be configured for Tk

ImportError: DLL load failed: The specified module could not be found.
```
How can I resolve it ?


Discussion:
Hey @superuser789, thanks for reporting. We'll take a look at that in our 4.2.2 version, to be released on February of next year (unfortunately 4.2.1 is almost ready to be released).
Note: Check https://github.com/takluyver/pynsist/issues/124 . More specifically: https://github.com/takluyver/pynsist/issues/124#issuecomment-370384973 (that should be enough)

Resolving patch:
diff --git a/installers/Windows/assets/jedi/__main__.py b/installers/Windows/assets/jedi/__main__.py
deleted file mode 100644
--- a/installers/Windows/assets/jedi/__main__.py
+++ /dev/null
@@ -1,57 +0,0 @@
-import os
-import sys
-
-
-def _get_paths():
-    # Get the path to jedi.
-    _d = os.path.dirname
-    _jedi_path = _d(_d(_d(_d(_d(__file__)))))
-    _parso_path = sys.argv[1]
-    # The paths are the directory that jedi and parso lie in.
-    return {'jedi': _jedi_path, 'parso': _parso_path}
-
-
-# Remove the first entry, because it's simply a directory entry that equals
-# this directory.
-# NOTE: Commented out line to be able to run jedi with the default interpreter
-# of the Spyder installation
-# del sys.path[0]
-
-if sys.version_info > (3, 4):
-    from importlib.machinery import PathFinder
-
-    class _ExactImporter(object):
-        def __init__(self, path_dct):
-            self._path_dct = path_dct
-
-        def find_module(self, fullname, path=None):
-            if path is None and fullname in self._path_dct:
-                p = self._path_dct[fullname]
-                loader = PathFinder.find_module(fullname, path=[p])
-                return loader
-            return None
-
-    # Try to import jedi/parso.
-    sys.meta_path.insert(0, _ExactImporter(_get_paths()))
-    from jedi.inference.compiled import subprocess  # NOQA
-    sys.meta_path.pop(0)
-else:
-    import imp
-
-    def load(name):
-        paths = list(_get_paths().values())
-        fp, pathname, description = imp.find_module(name, paths)
-        return imp.load_module(name, fp, pathname, description)
-
-    load('parso')
-    load('jedi')
-    from jedi.inference.compiled import subprocess  # NOQA
-
-from jedi._compatibility import highest_pickle_protocol  # noqa: E402
-
-
-# Retrieve the pickle protocol.
-host_sys_version = [int(x) for x in sys.argv[2].split('.')]
-pickle_protocol = highest_pickle_protocol([sys.version_info, host_sys_version])
-# And finally start the client.
-subprocess.Listener(pickle_protocol=pickle_protocol).listen()
diff --git a/installers/Windows/installer.py b/installers/Windows/installer.py
--- a/installers/Windows/installer.py
+++ b/installers/Windows/installer.py
@@ -25,14 +25,24 @@
 import argparse
 import importlib.util as iutil
 import os
+import requests
 import shutil
 import subprocess
 import sys
 import tempfile
+import zipfile
 
 import yarg
 
 
+# URL to download assets that the installer needs and that will be put on
+# the assets directory when building the installer
+
+ASSETS_URL = os.environ.get(
+    'ASSETS_URL',
+    'https://github.com/spyder-ide/windows-installer-assets/'
+    'releases/download/0.0.1/assets.zip')
+
 # Packages to remove from the requirements for example pip or
 # external direct dependencies (python-language-server spyder-kernels)
 
@@ -91,6 +101,9 @@
     {packages}
 files=black-20.8b1.dist-info > $INSTDIR/pkgs
     __main__.py > $INSTDIR/pkgs/jedi/inference/compiled/subprocess
+    lib
+    tcl86t.dll > $INSTDIR/pkgs
+    tk86t.dll > $INSTDIR/pkgs
 [Build]
 installer_name={installer_name}
 nsi_template={template}
@@ -281,6 +294,27 @@ def create_pynsist_cfg(
     return installer_exe
 
 
+def download_file(url, target_directory):
+    """
+    Download the URL to the target_directory and return the filename.
+    """
+    local_filename = os.path.join(target_directory, url.split("/")[-1])
+    r = requests.get(url, stream=True)
+    with open(local_filename, "wb") as f:
+        for chunk in r.iter_content(chunk_size=1024):
+            if chunk:  # filter out keep-alive new chunks
+                f.write(chunk)
+    return local_filename
+
+
+def unzip_file(filename, target_directory):
+    """
+    Given a filename, unzip it into the given target_directory.
+    """
+    with zipfile.ZipFile(filename) as z:
+        z.extractall(target_directory)
+
+
 def run(python_version, bitness, repo_root, entrypoint, package, icon_path,
         license_path, extra_packages=None, conda_path=None, suffix=None,
         template=None):
@@ -292,6 +326,13 @@ def run(python_version, bitness, repo_root, entrypoint, package, icon_path,
     (locking the dependencies set in setup.py) is generated and pynsist runned.
     """
     try:
+        print("Setting up assets from", ASSETS_URL)
+        print("Downloading assets from ", ASSETS_URL)
+        filename = download_file(ASSETS_URL, 'installers/Windows/assets')
+
+        print("Unzipping assets to", 'installers/Windows/assets')
+        unzip_file(filename, 'installers/Windows/assets')
+
         with tempfile.TemporaryDirectory(
                 prefix="installer-pynsist-") as work_dir:
             print("Temporary working directory at", work_dir)
@@ -310,6 +351,17 @@ def run(python_version, bitness, repo_root, entrypoint, package, icon_path,
                 "installers/Windows/assets/jedi/__main__.py",
                 os.path.join(work_dir, "__main__.py"))
 
+            print("Copying required assets for Tkinter to work")
+            shutil.copytree(
+                "installers/Windows/assets/tcl/lib",
+                os.path.join(work_dir, "lib"))
+            shutil.copy(
+                "installers/Windows/assets/tcl/tcl86t.dll",
+                os.path.join(work_dir, "tcl86t.dll"))
+            shutil.copy(
+                "installers/Windows/assets/tcl/tk86t.dll",
+                os.path.join(work_dir, "tk86t.dll"))
+
             print("Copying NSIS plugins into discoverable path")
             shutil.copy(
                 "installers/Windows/assets/nsist/plugins/x86-unicode/"


Test patch: