From b90825120922d0066f77636fce95de826d05aaf4 2013-11-05 19:56:40 From: Min RK Date: 2013-11-05 19:56:40 Subject: [PATCH] Merge pull request #4473 from takluyver/setup23 update script generation in setup.py These changes: - Give us scripts called `ipython` and (`ipython2` or `ipython3`) for any installation. (and likewise for each of iptest, ipcontroller, etc.) - Add a new `setup.py symlink` target, to use instead of `develop`, which installs scripts and symlinks the library into site-packages, without using setuptools. - Removes the static script entry points - all our entry points are now automatically generated. --- diff --git a/IPython/parallel/scripts/__init__.py b/IPython/parallel/scripts/__init__.py deleted file mode 100644 index fcdb8a9..0000000 --- a/IPython/parallel/scripts/__init__.py +++ /dev/null @@ -1,16 +0,0 @@ -# encoding: utf-8 - -"""""" - -__docformat__ = "restructuredtext en" - -#------------------------------------------------------------------------------- -# Copyright (C) 2008-2011 The IPython Development Team -# -# Distributed under the terms of the BSD License. The full license is in -# the file COPYING, distributed as part of this software. -#------------------------------------------------------------------------------- - -#------------------------------------------------------------------------------- -# Imports -#------------------------------------------------------------------------------- \ No newline at end of file diff --git a/IPython/parallel/scripts/ipcluster b/IPython/parallel/scripts/ipcluster deleted file mode 100755 index 7c8cfa2..0000000 --- a/IPython/parallel/scripts/ipcluster +++ /dev/null @@ -1,16 +0,0 @@ -#!/usr/bin/env python -# encoding: utf-8 - -#----------------------------------------------------------------------------- -# Copyright (C) 2008-2009 The IPython Development Team -# -# Distributed under the terms of the BSD License. The full license is in -# the file COPYING, distributed as part of this software. -#----------------------------------------------------------------------------- - -#----------------------------------------------------------------------------- -# Imports -#----------------------------------------------------------------------------- -from IPython.parallel.apps.ipclusterapp import launch_new_instance - -launch_new_instance() diff --git a/IPython/parallel/scripts/ipcontroller b/IPython/parallel/scripts/ipcontroller deleted file mode 100755 index 51346a7..0000000 --- a/IPython/parallel/scripts/ipcontroller +++ /dev/null @@ -1,16 +0,0 @@ -#!/usr/bin/env python -# encoding: utf-8 - -#----------------------------------------------------------------------------- -# Copyright (C) 2008-2009 The IPython Development Team -# -# Distributed under the terms of the BSD License. The full license is in -# the file COPYING, distributed as part of this software. -#----------------------------------------------------------------------------- - -#----------------------------------------------------------------------------- -# Imports -#----------------------------------------------------------------------------- -from IPython.parallel.apps.ipcontrollerapp import launch_new_instance - -launch_new_instance() diff --git a/IPython/parallel/scripts/ipengine b/IPython/parallel/scripts/ipengine deleted file mode 100755 index aa09d93..0000000 --- a/IPython/parallel/scripts/ipengine +++ /dev/null @@ -1,16 +0,0 @@ -#!/usr/bin/env python -# encoding: utf-8 - -#----------------------------------------------------------------------------- -# Copyright (C) 2008-2009 The IPython Development Team -# -# Distributed under the terms of the BSD License. The full license is in -# the file COPYING, distributed as part of this software. -#----------------------------------------------------------------------------- - -#----------------------------------------------------------------------------- -# Imports -#----------------------------------------------------------------------------- -from IPython.parallel.apps.ipengineapp import launch_new_instance - -launch_new_instance() diff --git a/IPython/parallel/scripts/iplogger b/IPython/parallel/scripts/iplogger deleted file mode 100755 index b86dfce..0000000 --- a/IPython/parallel/scripts/iplogger +++ /dev/null @@ -1,16 +0,0 @@ -#!/usr/bin/env python -# encoding: utf-8 - -#----------------------------------------------------------------------------- -# Copyright (C) 2008-2009 The IPython Development Team -# -# Distributed under the terms of the BSD License. The full license is in -# the file COPYING, distributed as part of this software. -#----------------------------------------------------------------------------- - -#----------------------------------------------------------------------------- -# Imports -#----------------------------------------------------------------------------- -from IPython.parallel.apps.iploggerapp import launch_new_instance - -launch_new_instance() diff --git a/IPython/scripts/__init__.py b/IPython/scripts/__init__.py deleted file mode 100644 index e69de29..0000000 --- a/IPython/scripts/__init__.py +++ /dev/null diff --git a/IPython/scripts/iptest b/IPython/scripts/iptest deleted file mode 100755 index b6d523c..0000000 --- a/IPython/scripts/iptest +++ /dev/null @@ -1,24 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -"""IPython Test Suite Runner. -""" - -# The tests can't even run if nose isn't available, so might as well give the -# user a civilized error message in that case. - -try: - import nose -except ImportError: - error = """\ -ERROR: The IPython test suite requires nose to run. - -Please install nose on your system first and try again. -For information on installing nose, see: -http://somethingaboutorange.com/mrl/projects/nose - -Exiting.""" - import sys - print >> sys.stderr, error -else: - from IPython.testing import iptestcontroller - iptestcontroller.main() diff --git a/IPython/scripts/ipython b/IPython/scripts/ipython deleted file mode 100755 index 614a678..0000000 --- a/IPython/scripts/ipython +++ /dev/null @@ -1,6 +0,0 @@ -#!/usr/bin/env python -"""Terminal-based IPython entry point. -""" - -from IPython import start_ipython -start_ipython() diff --git a/IPython/scripts/irunner b/IPython/scripts/irunner deleted file mode 100755 index 9406e27..0000000 --- a/IPython/scripts/irunner +++ /dev/null @@ -1,9 +0,0 @@ -#!/usr/bin/env python - -"""Thin wrapper around the IPython irunner module. - -Run with --help for details, or see the irunner source.""" - -from IPython.lib import irunner - -irunner.main() diff --git a/setup.py b/setup.py index fd4b36d..020547f 100755 --- a/setup.py +++ b/setup.py @@ -58,8 +58,8 @@ from setupbase import ( setup_args, find_packages, find_package_data, - find_scripts, - build_scripts_rename, + find_entry_points, + build_scripts_entrypt, find_data_files, check_for_dependencies, git_prebuild, @@ -68,6 +68,9 @@ from setupbase import ( require_submodules, UpdateSubmodules, CompileCSS, + install_symlinked, + install_lib_symlink, + install_scripts_for_symlink, ) from setupext import setupext @@ -148,7 +151,6 @@ require_clean_submodules() # update the manuals when building a source dist if len(sys.argv) >= 2 and sys.argv[1] in ('sdist','bdist_rpm'): - import textwrap # List of things to be updated. Each entry is a triplet of args for # target_update() @@ -231,6 +233,9 @@ setup_args['cmdclass'] = { 'upload_wininst' : UploadWindowsInstallers, 'submodule' : UpdateSubmodules, 'css' : CompileCSS, + 'symlink': install_symlinked, + 'install_lib_symlink': install_lib_symlink, + 'install_scripts_sym': install_scripts_for_symlink, } #--------------------------------------------------------------------------- @@ -263,7 +268,7 @@ if 'setuptools' in sys.modules: setup_args['cmdclass']['develop'] = require_submodules(develop) setuptools_extra_args['zip_safe'] = False - setuptools_extra_args['entry_points'] = find_scripts(True, suffix = '3' if PY3 else '') + 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'], @@ -316,10 +321,10 @@ else: # check for dependencies an inform the user what is needed. This is # just to make life easy for users. check_for_dependencies() - setup_args['scripts'] = find_scripts(False) - if PY3: - # Rename scripts with '3' suffix - setup_args['cmdclass']['build_scripts'] = build_scripts_rename + # 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()] + + setup_args['cmdclass']['build_scripts'] = build_scripts_entrypt #--------------------------------------------------------------------------- # Do the actual setup now diff --git a/setupbase.py b/setupbase.py index 54ab2ad..58c3c87 100644 --- a/setupbase.py +++ b/setupbase.py @@ -20,15 +20,14 @@ from __future__ import print_function #------------------------------------------------------------------------------- # Imports #------------------------------------------------------------------------------- +import errno import os import sys -try: - from configparser import ConfigParser -except: - from ConfigParser import ConfigParser from distutils.command.build_py import build_py from distutils.command.build_scripts import build_scripts +from distutils.command.install import install +from distutils.command.install_scripts import install_scripts from distutils.cmd import Command from glob import glob from subprocess import call @@ -311,7 +310,7 @@ def target_update(target,deps,cmd): # Find scripts #--------------------------------------------------------------------------- -def find_scripts(entry_points=False, suffix=''): +def find_entry_points(): """Find IPython's scripts. if entry_points is True: @@ -322,8 +321,7 @@ def find_scripts(entry_points=False, suffix=''): suffix is appended to script names if entry_points is True, so that the Python 3 scripts get named "ipython3" etc. """ - if entry_points: - console_scripts = [s % suffix for s in [ + ep = [ 'ipython%s = IPython:start_ipython', 'ipcontroller%s = IPython.parallel.apps.ipcontrollerapp:launch_new_instance', 'ipengine%s = IPython.parallel.apps.ipengineapp:launch_new_instance', @@ -331,37 +329,86 @@ def find_scripts(entry_points=False, suffix=''): 'ipcluster%s = IPython.parallel.apps.ipclusterapp:launch_new_instance', 'iptest%s = IPython.testing.iptestcontroller:main', 'irunner%s = IPython.lib.irunner:main', - ]] - gui_scripts = [] - scripts = dict(console_scripts=console_scripts, gui_scripts=gui_scripts) - else: - parallel_scripts = pjoin('IPython','parallel','scripts') - main_scripts = pjoin('IPython','scripts') - scripts = [ - pjoin(parallel_scripts, 'ipengine'), - pjoin(parallel_scripts, 'ipcontroller'), - pjoin(parallel_scripts, 'ipcluster'), - pjoin(parallel_scripts, 'iplogger'), - pjoin(main_scripts, 'ipython'), - pjoin(main_scripts, 'irunner'), - pjoin(main_scripts, 'iptest') ] - return scripts + suffix = str(sys.version_info[0]) + return [e % '' for e in ep] + [e % suffix for e in ep] + +script_src = """#!{executable} +# This script was automatically generated by setup.py +from {mod} import {func} +{func}() +""" + +class build_scripts_entrypt(build_scripts): + def run(self): + self.mkpath(self.build_dir) + outfiles = [] + for script in find_entry_points(): + name, entrypt = script.split('=') + name = name.strip() + entrypt = entrypt.strip() + outfile = os.path.join(self.build_dir, name) + outfiles.append(outfile) + print('Writing script to', outfile) + + mod, func = entrypt.split(':') + with open(outfile, 'w') as f: + f.write(script_src.format(executable=sys.executable, + mod=mod, func=func)) + + return outfiles, outfiles + +class install_lib_symlink(Command): + user_options = [ + ('install-dir=', 'd', "directory to install to"), + ] + + def initialize_options(self): + self.install_dir = None -class build_scripts_rename(build_scripts): - """Use this on Python 3 to rename scripts to ipython3 etc.""" - _suffix = '3' + def finalize_options(self): + self.set_undefined_options('symlink', + ('install_lib', 'install_dir'), + ) + + def run(self): + if sys.platform == 'win32': + raise Exception("This doesn't work on Windows.") + pkg = os.path.join(os.getcwd(), 'IPython') + dest = os.path.join(self.install_dir, 'IPython') + print('symlinking %s -> %s' % (pkg, dest)) + try: + os.symlink(pkg, dest) + except OSError as e: + if e.errno == errno.EEXIST: + print('ALREADY EXISTS') + else: + raise + +class install_symlinked(install): + def run(self): + if sys.platform == 'win32': + raise Exception("This doesn't work on Windows.") + install.run(self) - def copy_scripts(self): - outfiles, updated_files = super(build_scripts_rename, self).copy_scripts() - new_outfiles = [p + self._suffix for p in outfiles] - updated_files = [p + self._suffix for p in updated_files] - for old, new in zip(outfiles, new_outfiles): - if os.path.exists(new): - os.unlink(new) - self.move_file(old, new) - return new_outfiles, updated_files - + # 'sub_commands': a list of commands this command might have to run to + # get its work done. See cmd.py for more info. + sub_commands = [('install_lib_symlink', lambda self:True), + ('install_scripts_sym', lambda self:True), + ] + +class install_scripts_for_symlink(install_scripts): + """Redefined to get options from 'symlink' instead of 'install'. + + I love distutils almost as much as I love setuptools. + """ + def finalize_options(self): + self.set_undefined_options('build', ('build_scripts', 'build_dir')) + self.set_undefined_options('symlink', + ('install_scripts', 'install_dir'), + ('force', 'force'), + ('skip_build', 'skip_build'), + ) #--------------------------------------------------------------------------- # Verify all dependencies