Rework setup to allow installing on Python 2 and 3.

Scripts named ipython and ipython[23], etc.

Neither distutils nor setuptools made this easy.
This commit is contained in:
Thomas Kluyver 2013-10-31 17:38:34 -07:00
parent c4b9e69793
commit 883c46b5b5
2 changed files with 94 additions and 43 deletions

View File

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

View File

@ -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,85 @@ 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]
class build_scripts_rename(build_scripts):
"""Use this on Python 3 to rename scripts to ipython3 etc."""
_suffix = '3'
script_src = """#!{executable}
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
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