Repository: bokeh/bokeh

Issue:
Bokeh thinks site_packages is in conda root on Windows
In `setup.py` we have logic that tries to establish the location of `site_packages`, apparently this fails on Windows. In effect, we fail at removing any existing bokeh installs when using `develop`, and the `bokeh.pth` is written in the root of the conda env.



Discussion:

Resolving patch:
diff --git a/setup.py b/setup.py
--- a/setup.py
+++ b/setup.py
@@ -18,6 +18,14 @@
 import os, platform, re, shutil, site, subprocess, sys, time
 from os.path import abspath, dirname, exists, isdir, join, realpath, relpath
 from shutil import copy
+import sys
+
+if 'install' in sys.argv and sys.platform.startswith('win'):
+    # Try use setuptools, so that entry_points is handled, creating a bokeh.exe
+    try:
+        import setuptools
+    except ImportError:
+        pass
 
 try:
     import colorama
@@ -39,6 +47,7 @@ def yellow(text) : return text
 
 if 'nightly' in sys.argv:
     from setuptools import setup
+
     sys.argv.remove('nightly')
 
     with open('__conda_version__.txt', 'r') as f:
@@ -168,7 +177,8 @@ def getsitepackages():
                                              "dist-packages"))
             sitedirs.append(os.path.join(prefix, "lib", "dist-python"))
         else:
-            sitedirs = [prefix, os.path.join(prefix, "lib", "site-packages")]
+            sitedirs = [os.path.join(prefix, "lib", "site-packages"), prefix]
+
         if sys.platform == 'darwin':
             # for framework builds *only* we add the standard Apple
             # locations. Currently only per-user, but /Library and
@@ -190,21 +200,32 @@ def getsitepackages():
 
 
 def check_remove_bokeh_install(site_packages):
-    bokeh_path = join(site_packages, "bokeh")
-    if not (exists(bokeh_path) and isdir(bokeh_path)):
+    old_bokeh_files = []
+    for d in os.listdir(site_packages):
+        bokeh_path = join(site_packages, d)
+        if not (d == 'bokeh' or d.startswith('bokeh-')):
+            continue
+        old_bokeh_files.append(bokeh_path)
+
+    if len(old_bokeh_files) == 0:
         return
-    prompt = "Found existing bokeh install: %s\nRemove it? [y|N] " % bokeh_path
-    val = input(prompt)
+
+    print("Found old Bokeh files:")
+    for path in old_bokeh_files:
+        print(" - %s" % path)
+    val = input("Remove %s? [y|N] " % ("it" if len(old_bokeh_files)==1 else "them",))
     if val == "y":
-        print("Removing old bokeh install...", end=" ")
-        try:
-            shutil.rmtree(bokeh_path)
-            print("Done")
-        except (IOError, OSError):
-            print("Unable to remove old bokeh at %s, exiting" % bokeh_path)
-            sys.exit(-1)
+        print("Removing old Bokeh files...", end=" ")
+        for path in old_bokeh_files:
+            try:
+                if isdir(path): shutil.rmtree(path)
+                else: os.remove(path)
+            except (IOError, OSError) as e:
+                print(bright(red("\nUnable to remove old Bokeh file at %s, exiting" % path)) + " [reason: %s]" % e)
+                sys.exit(-1)
+        print("Done")
     else:
-        print("Not removing old bokeh install")
+        print(bright(red("Old Bokeh files not removed, exiting.")))
         sys.exit(1)
 
 
@@ -213,7 +234,7 @@ def remove_bokeh_pth(path_file):
         try:
             os.remove(path_file)
         except (IOError, OSError):
-            print("Unable to remove old path file at %s, exiting" % path_file)
+            print(bright(red("Unable to remove old path file at %s, exiting" % path_file)))
             sys.exit(-1)
         return True
     return False
@@ -401,6 +422,16 @@ def parse_jsargs():
 
     return jsbuild
 
+
+def package_tree(pkgroot):
+    """ Get list of packages by walking the directory structure and
+    including all dirs that have an __init__.py or are named test.
+    """
+    subdirs = [os.path.relpath(i[0], ROOT).replace(os.path.sep, '.')
+               for i in os.walk(os.path.join(ROOT, pkgroot))
+               if '__init__.py' in i[2]]
+    return subdirs
+
 # -----------------------------------------------------------------------------
 # Main script
 # -----------------------------------------------------------------------------
@@ -472,6 +503,8 @@ def parse_jsargs():
 
 print()
 if 'develop' in sys.argv:
+    # Note that setuptools supports 'develop' too, but we roll our own implementation
+    # that removes any existing Bokeh installation, and works in virtualenv
     if exists('bokeh/__conda_version__.py'):
         print(bright(red("ERROR:")) + " Detected a __conda_version__.py file, exiting")
         sys.exit(1)
@@ -527,7 +560,7 @@ def parse_jsargs():
 _version = versioneer.get_version()
 _cmdclass = versioneer.get_cmdclass()
 
-# Horrible hack: workaround to allow creation of bdist_whell on pip installation
+# Horrible hack: workaround to allow creation of bdist_wheel on pip installation
 # Why, for God's sake, is pip forcing the generation of wheels when installing a package?
 
 try:
@@ -539,58 +572,25 @@ def parse_jsargs():
 if bdist_wheel is not None:
     _cmdclass["bdist_wheel"] = bdist_wheel
 
+# Note on scripts and entry points. The 'scripts' value is handled by
+# distutils but does not provide an .exe, making it not very useful on
+# Windows. The 'entry_points' value is handled only if setuptools is
+# used, and does make an .exe. Note that in our conda recipe, we
+# seperately define an entry point.
+
 setup(
     name='bokeh',
     version=_version,
     cmdclass=_cmdclass,
-    packages=[
-        'bokeh',
-        'bokeh.application',
-        'bokeh.application.tests',
-        'bokeh.application.handlers',
-        'bokeh.application.handlers.tests',
-        'bokeh.models',
-        'bokeh.models.tests',
-        'bokeh.models.widgets',
-        'bokeh.charts',
-        'bokeh.charts.builders',
-        'bokeh.charts.builders.tests',
-        'bokeh.charts.tests',
-        'bokeh.client',
-        'bokeh.command',
-        'bokeh.command.tests',
-        'bokeh.command.subcommands',
-        'bokeh.command.subcommands.tests',
-        'bokeh.core',
-        'bokeh.core.compat',
-        'bokeh.core.compat.mplexporter',
-        'bokeh.core.compat.mplexporter.renderers',
-        'bokeh.core.tests',
-        'bokeh.core.validation',
-        'bokeh.plotting',
-        'bokeh.plotting.tests',
-        'bokeh.sampledata',
-        'bokeh.server',
-        'bokeh.server.protocol',
-        'bokeh.server.protocol.messages',
-        'bokeh.server.protocol.messages.tests',
-        'bokeh.server.protocol.tests',
-        'bokeh.server.tests',
-        'bokeh.server.views',
-        'bokeh.sphinxext',
-        'bokeh.themes',
-        'bokeh.tests',
-        'bokeh.util',
-        'bokeh.util.tests',
-    ],
+    packages=package_tree('bokeh'),
     package_data={'bokeh': package_data},
-    entry_points={'console_scripts': ['bokeh = bokeh.__main__:main',], },
     author='Continuum Analytics',
     author_email='info@continuum.io',
     url='http://github.com/bokeh/bokeh',
     description='Statistical and novel interactive HTML plots for Python',
     license='New BSD',
     scripts=['bin/bokeh', 'bin/bokeh-server'],
+    entry_points={'console_scripts': ['bokeh = bokeh.__main__:main',], },
     zip_safe=False,
     install_requires=REQUIRES
 )


Test patch:
diff --git a/bokeh/application/handlers/tests/__init__.py b/bokeh/application/handlers/tests/__init__.py
new file mode 100644
diff --git a/bokeh/application/tests/__init__.py b/bokeh/application/tests/__init__.py
new file mode 100644
diff --git a/bokeh/charts/builders/tests/__init__.py b/bokeh/charts/builders/tests/__init__.py
new file mode 100644
diff --git a/bokeh/charts/tests/__init__.py b/bokeh/charts/tests/__init__.py
new file mode 100644
diff --git a/bokeh/command/tests/__init__.py b/bokeh/command/tests/__init__.py
new file mode 100644
diff --git a/bokeh/models/tests/__init__.py b/bokeh/models/tests/__init__.py
new file mode 100644
diff --git a/bokeh/tests/__init__.py b/bokeh/tests/__init__.py
new file mode 100644
diff --git a/bokeh/util/tests/__init__.py b/bokeh/util/tests/__init__.py
new file mode 100644