From d4d34f7fd41da846ed098a6386a28e43efe6e5e7 2014-02-05 01:39:12
From: Thomas Kluyver <takowl@gmail.com>
Date: 2014-02-05 01:39:12
Subject: [PATCH] Merge pull request #4975 from minrk/t2pp

setup.py changes for 2.0
---

diff --git a/MANIFEST.in b/MANIFEST.in
index 7a26f03..d34b8e9 100644
--- a/MANIFEST.in
+++ b/MANIFEST.in
@@ -35,3 +35,4 @@ global-exclude *.pyc
 global-exclude *.pyo
 global-exclude .dircopy.log
 global-exclude .git
+global-exclude .ipynb_checkpoints
diff --git a/setup.py b/setup.py
index 5c65400..587a6e3 100755
--- a/setup.py
+++ b/setup.py
@@ -67,6 +67,7 @@ from setupbase import (
     update_submodules,
     require_submodules,
     UpdateSubmodules,
+    get_bdist_wheel,
     CompileCSS,
     JavascriptVersion,
     install_symlinked,
@@ -242,8 +243,8 @@ setup_args['cmdclass'] = {
 # For some commands, use setuptools.  Note that we do NOT list install here!
 # If you want a setuptools-enhanced install, just run 'setupegg.py install'
 needs_setuptools = set(('develop', 'release', 'bdist_egg', 'bdist_rpm',
-           'bdist', 'bdist_dumb', 'bdist_wininst', 'install_egg_info',
-           'egg_info', 'easy_install', 'upload',
+           'bdist', 'bdist_dumb', 'bdist_wininst', 'bdist_wheel',
+           'egg_info', 'easy_install', 'upload', 'install_egg_info',
             ))
 if sys.platform == 'win32':
     # Depend on setuptools for install on *Windows only*
@@ -259,43 +260,38 @@ if len(needs_setuptools.intersection(sys.argv)) > 0:
 # specific to setup
 setuptools_extra_args = {}
 
+# setuptools requirements
+
+extras_require = dict(
+    parallel = ['pyzmq>=2.1.11'],
+    qtconsole = ['pyzmq>=2.1.11', 'pygments'],
+    zmq = ['pyzmq>=2.1.11'],
+    doc = ['Sphinx>=1.1', 'numpydoc'],
+    test = ['nose>=0.10.1'],
+    notebook = ['tornado>=3.1', 'pyzmq>=2.1.11', 'jinja2'],
+    nbconvert = ['pygments', 'jinja2', 'Sphinx>=0.3']
+)
+everything = set()
+for deps in extras_require.values():
+    everything.update(deps)
+extras_require['all'] = everything
+install_requires = []
+if sys.platform == 'darwin':
+    install_requires.append('readline')
+elif sys.platform.startswith('win'):
+    # Pyreadline has unicode and Python 3 fixes in 2.0
+    install_requires.append('pyreadline>=2.0')
+
 if 'setuptools' in sys.modules:
     # setup.py develop should check for submodules
     from setuptools.command.develop import develop
     setup_args['cmdclass']['develop'] = require_submodules(develop)
+    setup_args['cmdclass']['bdist_wheel'] = get_bdist_wheel()
     
     setuptools_extra_args['zip_safe'] = False
     setuptools_extra_args['entry_points'] = {'console_scripts':find_entry_points()}
-    setup_args['extras_require'] = dict(
-        parallel = 'pyzmq>=2.1.11',
-        qtconsole = ['pyzmq>=2.1.11', 'pygments'],
-        zmq = 'pyzmq>=2.1.11',
-        doc = ['Sphinx>=1.1', 'numpydoc'],
-        test = 'nose>=0.10.1',
-        notebook = ['tornado>=3.1', 'pyzmq>=2.1.11', 'jinja2'],
-        nbconvert = ['pygments', 'jinja2', 'Sphinx>=0.3']
-    )
-    everything = set()
-    for deps in setup_args['extras_require'].values():
-        if not isinstance(deps, list):
-            deps = [deps]
-        for dep in deps:
-            everything.add(dep)
-    setup_args['extras_require']['all'] = everything
-    
-    requires = setup_args.setdefault('install_requires', [])
-    setupext.display_status = False
-    if not setupext.check_for_readline():
-        if sys.platform == 'darwin':
-            requires.append('readline')
-        elif sys.platform.startswith('win'):
-            # Pyreadline 64 bit windows issue solved in versions >=1.7.1
-            # Also solves issues with some older versions of pyreadline that
-            # satisfy the unconstrained depdendency.
-            requires.append('pyreadline>=1.7.1')
-        else:
-            pass
-            # do we want to install readline here?
+    setup_args['extras_require'] = extras_require
+    requires = setup_args['install_requires'] = install_requires
 
     # Script to be run by the windows binary installer after the default setup
     # routine, to add shortcuts and similar windows-only things.  Windows
@@ -314,10 +310,13 @@ if 'setuptools' in sys.modules:
                                   "ipython_win_post_install.py"}}
 
 else:
-    # If we are running without setuptools, call this function which will
+    # If we are installing without setuptools, call this function which will
     # check for dependencies an inform the user what is needed.  This is
     # just to make life easy for users.
-    check_for_dependencies()
+    for install_cmd in ('install', 'symlink'):
+        if install_cmd in sys.argv:
+            check_for_dependencies()
+            break
     # scripts has to be a non-empty list, or install_scripts isn't called
     setup_args['scripts'] = [e.split('=')[0].strip() for e in find_entry_points()]
 
diff --git a/setupbase.py b/setupbase.py
index b5c9b20..c15e9ab 100644
--- a/setupbase.py
+++ b/setupbase.py
@@ -127,23 +127,44 @@ def find_package_data():
     # This is not enough for these things to appear in an sdist.
     # We need to muck with the MANIFEST to get this to work
     
-    # exclude static things that we don't ship (e.g. mathjax)
-    excludes = ['mathjax']
+    # exclude components from the walk,
+    # we will build the components separately
+    excludes = ['components']
     
     # add 'static/' prefix to exclusions, and tuplify for use in startswith
-    excludes = tuple([os.path.join('static', ex) for ex in excludes])
+    excludes = tuple([pjoin('static', ex) for ex in excludes])
     
     # walk notebook resources:
     cwd = os.getcwd()
     os.chdir(os.path.join('IPython', 'html'))
-    static_walk = list(os.walk('static'))
     static_data = []
-    for parent, dirs, files in static_walk:
+    for parent, dirs, files in os.walk('static'):
         if parent.startswith(excludes):
             continue
         for f in files:
-            static_data.append(os.path.join(parent, f))
-
+            static_data.append(pjoin(parent, f))
+    components = pjoin("static", "components")
+    # select the components we actually need to install
+    # (there are lots of resources we bundle for sdist-reasons that we don't actually use)
+    static_data.extend([
+        pjoin(components, "backbone", "backbone-min.js"),
+        pjoin(components, "bootstrap", "bootstrap", "js", "bootstrap.min.js"),
+        pjoin(components, "font-awesome", "font", "*.*"),
+        pjoin(components, "highlight.js", "build", "highlight.pack.js"),
+        pjoin(components, "jquery", "jquery.min.js"),
+        pjoin(components, "jquery-ui", "ui", "minified", "jquery-ui.min.js"),
+        pjoin(components, "jquery-ui", "themes", "smoothness", "jquery-ui.min.css"),
+        pjoin(components, "marked", "lib", "marked.js"),
+        pjoin(components, "requirejs", "require.js"),
+        pjoin(components, "underscore", "underscore-min.js"),
+    ])
+    
+    # Ship all of Codemirror's CSS and JS
+    for parent, dirs, files in os.walk(pjoin(components, 'codemirror')):
+        for f in files:
+            if f.endswith(('.js', '.css')):
+                static_data.append(pjoin(parent, f))
+    
     os.chdir(os.path.join('tests',))
     js_tests = glob('casperjs/*.*') +  glob('casperjs/*/*')
 
@@ -157,7 +178,6 @@ def find_package_data():
         'IPython.config.profile' : ['README*', '*/*.py'],
         'IPython.core.tests' : ['*.png', '*.jpg'],
         'IPython.lib.tests' : ['*.wav'],
-        'IPython.testing' : ['*.txt'],
         'IPython.testing.plugin' : ['*.txt'],
         'IPython.html' : ['templates/*'] + static_data,
         'IPython.html.tests' : js_tests,
@@ -167,6 +187,17 @@ def find_package_data():
         'IPython.nbconvert.filters' : ['marked.js'],
         'IPython.nbformat' : ['tests/*.ipynb']
     }
+    
+    # verify that package_data makes sense
+    for pkg, data in package_data.items():
+        pkg_root = pjoin(*pkg.split('.'))
+        for d in data:
+            path = pjoin(pkg_root, d)
+            if '*' in path:
+                assert len(glob(path)) > 0, "No files match pattern %s" % path
+            else:
+                assert os.path.exists(path), "Missing package data: %s" % path
+
     return package_data
 
 
@@ -215,10 +246,9 @@ def find_data_files():
     """
     Find IPython's data_files.
 
-    Most of these are docs.
+    Just man pages at this point.
     """
 
-    docdirbase  = pjoin('share', 'doc', 'ipython')
     manpagebase = pjoin('share', 'man', 'man1')
 
     # Simple file lists can be made by hand
@@ -227,24 +257,8 @@ def find_data_files():
         # When running from a source tree, the manpages aren't gzipped
         manpages = [f for f in glob(pjoin('docs','man','*.1')) if isfile(f)]
 
-    igridhelpfiles = [f for f in glob(pjoin('IPython','extensions','igrid_help.*')) if isfile(f)]
-
-    # For nested structures, use the utility above
-    example_files = make_dir_struct(
-        'data',
-        pjoin('docs','examples'),
-        pjoin(docdirbase,'examples')
-    )
-    manual_files = make_dir_struct(
-        'data',
-        pjoin('docs','html'),
-        pjoin(docdirbase,'manual')
-    )
-
     # And assemble the entire output list
-    data_files = [ (manpagebase, manpages),
-                   (pjoin(docdirbase, 'extensions'), igridhelpfiles),
-                   ] + manual_files + example_files
+    data_files = [ (manpagebase, manpages) ]
 
     return data_files
 
@@ -452,7 +466,8 @@ def check_for_dependencies():
     check_for_sphinx()
     check_for_pygments()
     check_for_nose()
-    check_for_pexpect()
+    if os.name == 'posix':
+        check_for_pexpect()
     check_for_pyzmq()
     check_for_tornado()
     check_for_readline()
@@ -556,6 +571,70 @@ def require_submodules(command):
     return DecoratedCommand
 
 #---------------------------------------------------------------------------
+# bdist related
+#---------------------------------------------------------------------------
+
+def get_bdist_wheel():
+    """Construct bdist_wheel command for building wheels
+    
+    Constructs py2-none-any tag, instead of py2.7-none-any
+    """
+    class RequiresWheel(Command):
+        description = "Dummy command for missing bdist_wheel"
+        user_options = []
+
+        def initialize_options(self):
+            pass
+
+        def finalize_options(self):
+            pass
+
+        def run(self):
+            print("bdist_wheel requires the wheel package")
+            sys.exit(1)
+
+    if 'setuptools' not in sys.modules:
+        return RequiresWheel
+    else:
+        try:
+            from wheel.bdist_wheel import bdist_wheel, read_pkg_info, write_pkg_info
+        except ImportError:
+            return RequiresWheel
+        
+        class bdist_wheel_tag(bdist_wheel):
+
+            def get_tag(self):
+                return ('py%i' % sys.version_info[0], 'none', 'any')
+
+            def add_requirements(self, metadata_path):
+                """transform platform-dependent requirements"""
+                pkg_info = read_pkg_info(metadata_path)
+                # pkg_info is an email.Message object (?!)
+                # we have to remove the unconditional 'readline' and/or 'pyreadline' entries
+                # and transform them to conditionals
+                requires = pkg_info.get_all('Requires-Dist')
+                del pkg_info['Requires-Dist']
+                def _remove_startswith(lis, prefix):
+                    """like list.remove, but with startswith instead of =="""
+                    found = False
+                    for idx, item in enumerate(lis):
+                        if item.startswith(prefix):
+                            found = True
+                            break
+                    if found:
+                        lis.pop(idx)
+                
+                for pkg in ("readline", "pyreadline"):
+                    _remove_startswith(requires, pkg)
+                requires.append("readline; sys.platform == 'darwin'")
+                requires.append("pyreadline (>=2.0); sys.platform == 'win32'")
+                for r in requires:
+                    pkg_info['Requires-Dist'] = r
+                write_pkg_info(metadata_path, pkg_info)
+        
+        return bdist_wheel_tag
+
+#---------------------------------------------------------------------------
 # Notebook related
 #---------------------------------------------------------------------------
 
diff --git a/setupext/setupext.py b/setupext/setupext.py
index 468c457..38f6c43 100644
--- a/setupext/setupext.py
+++ b/setupext/setupext.py
@@ -107,7 +107,7 @@ def check_for_pexpect():
     try:
         import pexpect
     except ImportError:
-        print_status("pexpect", "no (required for running standalone doctests)")
+        print_status("pexpect", "no (will use bundled version in IPython.external)")
         return False
     else:
         print_status("pexpect", pexpect.__version__)