Show More
setupbase.py
730 lines
| 25.0 KiB
| text/x-python
|
PythonLexer
|
r1237 | # encoding: utf-8 | ||
|
r1239 | """ | ||
This module defines the things that are used in setup.py for building IPython | ||||
This includes: | ||||
* The basic arguments to setup | ||||
* Functions for finding things like packages, package data, etc. | ||||
* A function for checking dependencies. | ||||
""" | ||||
|
r1237 | |||
|
r16386 | # Copyright (c) IPython Development Team. | ||
# Distributed under the terms of the Modified BSD License. | ||||
from __future__ import print_function | ||||
|
r1237 | |||
|
r13452 | import errno | ||
|
r3198 | import os | ||
import sys | ||||
|
r1237 | |||
|
r17343 | from distutils import log | ||
|
r3198 | from distutils.command.build_py import build_py | ||
|
r13368 | from distutils.command.build_scripts import build_scripts | ||
|
r13452 | from distutils.command.install import install | ||
from distutils.command.install_scripts import install_scripts | ||||
|
r10484 | from distutils.cmd import Command | ||
|
r15830 | from fnmatch import fnmatch | ||
|
r1237 | from glob import glob | ||
|
r17333 | from subprocess import check_call | ||
|
r1237 | |||
from setupext import install_data_ext | ||||
#------------------------------------------------------------------------------- | ||||
# Useful globals and utility functions | ||||
#------------------------------------------------------------------------------- | ||||
# A few handy globals | ||||
isfile = os.path.isfile | ||||
pjoin = os.path.join | ||||
|
r10610 | repo_root = os.path.dirname(os.path.abspath(__file__)) | ||
|
r1237 | |||
def oscmd(s): | ||||
|
r3198 | print(">", s) | ||
|
r1237 | os.system(s) | ||
|
r4872 | |||
|
r6446 | # Py3 compatibility hacks, without assuming IPython itself is installed with | ||
# the full py3compat machinery. | ||||
|
r4750 | try: | ||
execfile | ||||
except NameError: | ||||
def execfile(fname, globs, locs=None): | ||||
locs = locs or globs | ||||
exec(compile(open(fname).read(), fname, "exec"), globs, locs) | ||||
|
r1237 | |||
# A little utility we'll need below, since glob() does NOT allow you to do | ||||
# exclusion on multiple endings! | ||||
def file_doesnt_endwith(test,endings): | ||||
"""Return true if test is a file and its name does NOT end with any | ||||
of the strings listed in endings.""" | ||||
if not isfile(test): | ||||
return False | ||||
for e in endings: | ||||
if test.endswith(e): | ||||
return False | ||||
return True | ||||
#--------------------------------------------------------------------------- | ||||
# Basic project information | ||||
#--------------------------------------------------------------------------- | ||||
|
r2146 | # release.py contains version, authors, license, url, keywords, etc. | ||
|
r10610 | execfile(pjoin(repo_root, 'IPython','core','release.py'), globals()) | ||
|
r1237 | |||
# Create a dict with the basic information | ||||
# This dict is eventually passed to setup after additional keys are added. | ||||
setup_args = dict( | ||||
name = name, | ||||
version = version, | ||||
description = description, | ||||
long_description = long_description, | ||||
author = author, | ||||
author_email = author_email, | ||||
url = url, | ||||
download_url = download_url, | ||||
license = license, | ||||
platforms = platforms, | ||||
keywords = keywords, | ||||
|
r4771 | classifiers = classifiers, | ||
|
r1237 | cmdclass = {'install_data': install_data_ext}, | ||
) | ||||
#--------------------------------------------------------------------------- | ||||
# Find packages | ||||
#--------------------------------------------------------------------------- | ||||
def find_packages(): | ||||
|
r1239 | """ | ||
Find all of IPython's packages. | ||||
""" | ||||
|
r6492 | excludes = ['deathrow', 'quarantine'] | ||
|
r4466 | packages = [] | ||
for dir,subdirs,files in os.walk('IPython'): | ||||
package = dir.replace(os.path.sep, '.') | ||||
|
r6492 | if any(package.startswith('IPython.'+exc) for exc in excludes): | ||
|
r4466 | # package is to be excluded (e.g. deathrow) | ||
continue | ||||
if '__init__.py' not in files: | ||||
# not a package | ||||
continue | ||||
packages.append(package) | ||||
|
r1237 | return packages | ||
#--------------------------------------------------------------------------- | ||||
# Find package data | ||||
#--------------------------------------------------------------------------- | ||||
def find_package_data(): | ||||
|
r1239 | """ | ||
Find IPython's package_data. | ||||
""" | ||||
|
r1237 | # This is not enough for these things to appear in an sdist. | ||
# We need to muck with the MANIFEST to get this to work | ||||
|
r5625 | |||
|
r15830 | # exclude components and less from the walk; | ||
|
r15031 | # we will build the components separately | ||
|
r15830 | excludes = [ | ||
pjoin('static', 'components'), | ||||
pjoin('static', '*', 'less'), | ||||
] | ||||
|
r5625 | |||
|
r4322 | # walk notebook resources: | ||
cwd = os.getcwd() | ||||
|
r11035 | os.chdir(os.path.join('IPython', 'html')) | ||
|
r4322 | static_data = [] | ||
|
r15031 | for parent, dirs, files in os.walk('static'): | ||
|
r15830 | if any(fnmatch(parent, pat) for pat in excludes): | ||
# prevent descending into subdirs | ||||
dirs[:] = [] | ||||
|
r5625 | continue | ||
|
r4322 | for f in files: | ||
|
r15031 | static_data.append(pjoin(parent, f)) | ||
|
r15830 | |||
|
r15031 | 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"), | ||||
|
r16985 | pjoin(components, "bootstrap", "js", "bootstrap.min.js"), | ||
|
r15815 | pjoin(components, "bootstrap-tour", "build", "css", "bootstrap-tour.min.css"), | ||
|
r15578 | pjoin(components, "bootstrap-tour", "build", "js", "bootstrap-tour.min.js"), | ||
|
r17410 | pjoin(components, "font-awesome", "fonts", "*.*"), | ||
|
r15659 | pjoin(components, "google-caja", "html-css-sanitizer-minified.js"), | ||
|
r15031 | 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"), | ||||
|
r16014 | pjoin(components, "jquery-ui", "themes", "smoothness", "images", "*"), | ||
|
r15031 | pjoin(components, "marked", "lib", "marked.js"), | ||
|
r15035 | pjoin(components, "requirejs", "require.js"), | ||
|
r15031 | pjoin(components, "underscore", "underscore-min.js"), | ||
|
r17474 | pjoin(components, "moment", "moment.js"), | ||
pjoin(components, "moment", "min","moment.min.js"), | ||||
|
r15031 | ]) | ||
# 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)) | ||||
|
r13276 | os.chdir(os.path.join('tests',)) | ||
|
r15203 | js_tests = glob('*.js') + glob('*/*.js') | ||
|
r14052 | |||
os.chdir(os.path.join(cwd, 'IPython', 'nbconvert')) | ||||
|
r14059 | nbconvert_templates = [os.path.join(dirpath, '*.*') | ||
|
r14052 | for dirpath, _, _ in os.walk('templates')] | ||
|
r13276 | os.chdir(cwd) | ||
|
r1317 | package_data = { | ||
|
r5247 | 'IPython.config.profile' : ['README*', '*/*.py'], | ||
|
r10805 | 'IPython.core.tests' : ['*.png', '*.jpg'], | ||
|
r13550 | 'IPython.lib.tests' : ['*.wav'], | ||
|
r7774 | 'IPython.testing.plugin' : ['*.txt'], | ||
|
r11035 | 'IPython.html' : ['templates/*'] + static_data, | ||
|
r13276 | 'IPython.html.tests' : js_tests, | ||
|
r11029 | 'IPython.qt.console' : ['resources/icon/*.svg'], | ||
|
r14052 | 'IPython.nbconvert' : nbconvert_templates + | ||
|
r17085 | [ | ||
'tests/files/*.*', | ||||
'exporters/tests/files/*.*', | ||||
|
r17101 | 'preprocessors/tests/files/*.*', | ||
|
r17085 | ], | ||
|
r14212 | 'IPython.nbconvert.filters' : ['marked.js'], | ||
|
r16367 | 'IPython.nbformat' : ['tests/*.ipynb','v3/v3.withref.json'] | ||
|
r1317 | } | ||
|
r15035 | |||
|
r15114 | return package_data | ||
|
r15165 | |||
|
r15114 | def check_package_data(package_data): | ||
"""verify that package_data globs make sense""" | ||||
print("checking package data") | ||||
|
r15035 | 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 | ||||
|
r1237 | |||
|
r15165 | def check_package_data_first(command): | ||
"""decorator for checking package_data before running a given command | ||||
Probably only needs to wrap build_py | ||||
""" | ||||
class DecoratedCommand(command): | ||||
def run(self): | ||||
check_package_data(self.package_data) | ||||
command.run(self) | ||||
return DecoratedCommand | ||||
|
r1237 | |||
#--------------------------------------------------------------------------- | ||||
# Find data files | ||||
#--------------------------------------------------------------------------- | ||||
|
r1525 | def make_dir_struct(tag,base,out_base): | ||
"""Make the directory structure of all files below a starting dir. | ||||
This is just a convenience routine to help build a nested directory | ||||
hierarchy because distutils is too stupid to do this by itself. | ||||
XXX - this needs a proper docstring! | ||||
""" | ||||
|
r4872 | |||
|
r1525 | # we'll use these a lot below | ||
lbase = len(base) | ||||
pathsep = os.path.sep | ||||
lpathsep = len(pathsep) | ||||
|
r4872 | |||
|
r1525 | out = [] | ||
for (dirpath,dirnames,filenames) in os.walk(base): | ||||
# we need to strip out the dirpath from the base to map it to the | ||||
# output (installation) path. This requires possibly stripping the | ||||
# path separator, because otherwise pjoin will not work correctly | ||||
# (pjoin('foo/','/bar') returns '/bar'). | ||||
|
r4872 | |||
|
r1525 | dp_eff = dirpath[lbase:] | ||
if dp_eff.startswith(pathsep): | ||||
dp_eff = dp_eff[lpathsep:] | ||||
|
r4872 | # The output path must be anchored at the out_base marker | ||
|
r1525 | out_path = pjoin(out_base,dp_eff) | ||
# Now we can generate the final filenames. Since os.walk only produces | ||||
# filenames, we must join back with the dirpath to get full valid file | ||||
# paths: | ||||
pfiles = [pjoin(dirpath,f) for f in filenames] | ||||
|
r3205 | # Finally, generate the entry we need, which is a pari of (output | ||
|
r1525 | # path, files) for use as a data_files parameter in install_data. | ||
|
r3205 | out.append((out_path, pfiles)) | ||
|
r1525 | |||
return out | ||||
|
r4872 | |||
|
r1525 | |||
|
r1237 | def find_data_files(): | ||
|
r1239 | """ | ||
Find IPython's data_files. | ||||
|
r1525 | |||
|
r15032 | Just man pages at this point. | ||
|
r1239 | """ | ||
|
r4872 | |||
|
r2058 | manpagebase = pjoin('share', 'man', 'man1') | ||
|
r4872 | |||
|
r1525 | # Simple file lists can be made by hand | ||
|
r8454 | manpages = [f for f in glob(pjoin('docs','man','*.1.gz')) if isfile(f)] | ||
|
r4420 | if not manpages: | ||
# When running from a source tree, the manpages aren't gzipped | ||||
|
r8454 | manpages = [f for f in glob(pjoin('docs','man','*.1')) if isfile(f)] | ||
|
r1525 | # And assemble the entire output list | ||
|
r15032 | data_files = [ (manpagebase, manpages) ] | ||
|
r3280 | |||
|
r1522 | return data_files | ||
|
r1237 | |||
|
r2100 | |||
def make_man_update_target(manpage): | ||||
"""Return a target_update-compliant tuple for the given manpage. | ||||
Parameters | ||||
---------- | ||||
manpage : string | ||||
Name of the manpage, must include the section number (trailing number). | ||||
Example | ||||
------- | ||||
>>> make_man_update_target('ipython.1') #doctest: +NORMALIZE_WHITESPACE | ||||
('docs/man/ipython.1.gz', | ||||
['docs/man/ipython.1'], | ||||
'cd docs/man && gzip -9c ipython.1 > ipython.1.gz') | ||||
""" | ||||
man_dir = pjoin('docs', 'man') | ||||
manpage_gz = manpage + '.gz' | ||||
manpath = pjoin(man_dir, manpage) | ||||
manpath_gz = pjoin(man_dir, manpage_gz) | ||||
gz_cmd = ( "cd %(man_dir)s && gzip -9c %(manpage)s > %(manpage_gz)s" % | ||||
locals() ) | ||||
return (manpath_gz, [manpath], gz_cmd) | ||||
|
r4872 | |||
|
r5828 | # The two functions below are copied from IPython.utils.path, so we don't need | ||
# to import IPython during setup, which fails on Python 3. | ||||
def target_outdated(target,deps): | ||||
"""Determine whether a target is out of date. | ||||
target_outdated(target,deps) -> 1/0 | ||||
deps: list of filenames which MUST exist. | ||||
target: single filename which may or may not exist. | ||||
If target doesn't exist or is older than any file listed in deps, return | ||||
true, otherwise return false. | ||||
""" | ||||
try: | ||||
target_time = os.path.getmtime(target) | ||||
except os.error: | ||||
return 1 | ||||
for dep in deps: | ||||
dep_time = os.path.getmtime(dep) | ||||
if dep_time > target_time: | ||||
#print "For target",target,"Dep failed:",dep # dbg | ||||
#print "times (dep,tar):",dep_time,target_time # dbg | ||||
return 1 | ||||
return 0 | ||||
def target_update(target,deps,cmd): | ||||
"""Update a target with a given command given a list of dependencies. | ||||
target_update(target,deps,cmd) -> runs cmd if target is outdated. | ||||
This is just a wrapper around target_outdated() which calls the given | ||||
command if target is outdated.""" | ||||
if target_outdated(target,deps): | ||||
|
r6144 | os.system(cmd) | ||
|
r5828 | |||
|
r1237 | #--------------------------------------------------------------------------- | ||
# Find scripts | ||||
#--------------------------------------------------------------------------- | ||||
|
r13452 | def find_entry_points(): | ||
|
r3681 | """Find IPython's scripts. | ||
|
r4872 | |||
|
r3681 | if entry_points is True: | ||
return setuptools entry_point-style definitions | ||||
else: | ||||
return file paths of plain scripts [default] | ||||
|
r4872 | |||
|
r4765 | suffix is appended to script names if entry_points is True, so that the | ||
Python 3 scripts get named "ipython3" etc. | ||||
|
r1239 | """ | ||
|
r13452 | ep = [ | ||
|
r11177 | 'ipython%s = IPython:start_ipython', | ||
|
r4765 | 'ipcontroller%s = IPython.parallel.apps.ipcontrollerapp:launch_new_instance', | ||
'ipengine%s = IPython.parallel.apps.ipengineapp:launch_new_instance', | ||||
'ipcluster%s = IPython.parallel.apps.ipclusterapp:launch_new_instance', | ||||
|
r12618 | 'iptest%s = IPython.testing.iptestcontroller:main', | ||
|
r3838 | ] | ||
|
r13452 | suffix = str(sys.version_info[0]) | ||
return [e % '' for e in ep] + [e % suffix for e in ep] | ||||
script_src = """#!{executable} | ||||
|
r13497 | # This script was automatically generated by setup.py | ||
|
r13845 | if __name__ == '__main__': | ||
|
r13861 | from {mod} import {func} | ||
|
r13845 | {func}() | ||
|
r13452 | """ | ||
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"), | ||||
] | ||||
|
r3838 | |||
|
r13452 | 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') | ||||
|
r13857 | if os.path.islink(dest): | ||
print('removing existing symlink at %s' % dest) | ||||
os.unlink(dest) | ||||
|
r13452 | print('symlinking %s -> %s' % (pkg, dest)) | ||
|
r13857 | os.symlink(pkg, dest) | ||
|
r13452 | |||
|
r13862 | class unsymlink(install): | ||
def run(self): | ||||
dest = os.path.join(self.install_lib, 'IPython') | ||||
if os.path.islink(dest): | ||||
print('removing symlink at %s' % dest) | ||||
os.unlink(dest) | ||||
else: | ||||
print('No symlink exists at %s' % dest) | ||||
|
r13452 | |||
class install_symlinked(install): | ||||
def run(self): | ||||
if sys.platform == 'win32': | ||||
raise Exception("This doesn't work on Windows.") | ||||
|
r13840 | |||
# Run all sub-commands (at least those that need to be run) | ||||
for cmd_name in self.get_sub_commands(): | ||||
self.run_command(cmd_name) | ||||
|
r13368 | |||
|
r13452 | # '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'), | ||||
) | ||||
|
r13368 | |||
|
r1237 | #--------------------------------------------------------------------------- | ||
|
r1525 | # Verify all dependencies | ||
|
r1237 | #--------------------------------------------------------------------------- | ||
def check_for_dependencies(): | ||||
|
r1239 | """Check for IPython's dependencies. | ||
|
r4872 | |||
|
r1239 | This function should NOT be called if running under setuptools! | ||
""" | ||||
|
r1237 | from setupext.setupext import ( | ||
|
r3198 | print_line, print_raw, print_status, | ||
|
r1237 | check_for_sphinx, check_for_pygments, | ||
|
r3634 | check_for_nose, check_for_pexpect, | ||
|
r11094 | check_for_pyzmq, check_for_readline, | ||
|
r12000 | check_for_jinja2, check_for_tornado | ||
|
r1237 | ) | ||
print_line() | ||||
print_raw("BUILDING IPYTHON") | ||||
print_status('python', sys.version) | ||||
print_status('platform', sys.platform) | ||||
if sys.platform == 'win32': | ||||
print_status('Windows version', sys.getwindowsversion()) | ||||
|
r4872 | |||
|
r1237 | print_raw("") | ||
print_raw("OPTIONAL DEPENDENCIES") | ||||
check_for_sphinx() | ||||
check_for_pygments() | ||||
check_for_nose() | ||||
|
r15026 | if os.name == 'posix': | ||
check_for_pexpect() | ||||
|
r3634 | check_for_pyzmq() | ||
|
r12000 | check_for_tornado() | ||
|
r3699 | check_for_readline() | ||
|
r11090 | check_for_jinja2() | ||
|
r3198 | |||
|
r10484 | #--------------------------------------------------------------------------- | ||
# VCS related | ||||
#--------------------------------------------------------------------------- | ||||
|
r10556 | # utils.submodule has checks for submodule status | ||
execfile(pjoin('IPython','utils','submodule.py'), globals()) | ||||
|
r10484 | |||
class UpdateSubmodules(Command): | ||||
"""Update git submodules | ||||
IPython's external javascript dependencies live in a separate repo. | ||||
""" | ||||
description = "Update git submodules" | ||||
user_options = [] | ||||
def initialize_options(self): | ||||
pass | ||||
def finalize_options(self): | ||||
pass | ||||
def run(self): | ||||
failure = False | ||||
try: | ||||
self.spawn('git submodule init'.split()) | ||||
self.spawn('git submodule update --recursive'.split()) | ||||
except Exception as e: | ||||
failure = e | ||||
print(e) | ||||
|
r10610 | if not check_submodule_status(repo_root) == 'clean': | ||
|
r10484 | print("submodules could not be checked out") | ||
sys.exit(1) | ||||
|
r10556 | |||
|
r10484 | |||
def git_prebuild(pkg_dir, build_cmd=build_py): | ||||
"""Return extended build or sdist command class for recording commit | ||||
|
r6315 | |||
records git commit in IPython.utils._sysinfo.commit | ||||
for use in IPython.utils.sysinfo.sys_info() calls after installation. | ||||
|
r10484 | |||
Also ensures that submodules exist prior to running | ||||
|
r3198 | """ | ||
|
r6315 | |||
|
r3198 | class MyBuildPy(build_cmd): | ||
''' Subclass to write commit data into installation tree ''' | ||||
def run(self): | ||||
|
r4900 | build_cmd.run(self) | ||
|
r7794 | # this one will only fire for build commands | ||
if hasattr(self, 'build_lib'): | ||||
self._record_commit(self.build_lib) | ||||
def make_release_tree(self, base_dir, files): | ||||
# this one will fire for sdist | ||||
build_cmd.make_release_tree(self, base_dir, files) | ||||
self._record_commit(base_dir) | ||||
def _record_commit(self, base_dir): | ||||
|
r3198 | import subprocess | ||
proc = subprocess.Popen('git rev-parse --short HEAD', | ||||
stdout=subprocess.PIPE, | ||||
stderr=subprocess.PIPE, | ||||
shell=True) | ||||
repo_commit, _ = proc.communicate() | ||||
|
r7794 | repo_commit = repo_commit.strip().decode("ascii") | ||
out_pth = pjoin(base_dir, pkg_dir, 'utils', '_sysinfo.py') | ||||
if os.path.isfile(out_pth) and not repo_commit: | ||||
# nothing to write, don't clobber | ||||
return | ||||
print("writing git commit '%s' to %s" % (repo_commit, out_pth)) | ||||
# remove to avoid overwriting original via hard link | ||||
try: | ||||
os.remove(out_pth) | ||||
except (IOError, OSError): | ||||
pass | ||||
|
r6449 | with open(out_pth, 'w') as out_file: | ||
out_file.writelines([ | ||||
|
r6446 | '# GENERATED BY setup.py\n', | ||
|
r18039 | 'commit = u"%s"\n' % repo_commit, | ||
|
r6449 | ]) | ||
|
r10556 | return require_submodules(MyBuildPy) | ||
|
r10484 | |||
def require_submodules(command): | ||||
"""decorator for instructing a command to check for submodules before running""" | ||||
class DecoratedCommand(command): | ||||
def run(self): | ||||
|
r10610 | if not check_submodule_status(repo_root) == 'clean': | ||
|
r10484 | print("submodules missing! Run `setup.py submodule` and try again") | ||
sys.exit(1) | ||||
command.run(self) | ||||
return DecoratedCommand | ||||
|
r12531 | |||
#--------------------------------------------------------------------------- | ||||
|
r15027 | # 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): | ||||
|
r15034 | description = "Dummy command for missing bdist_wheel" | ||
user_options = [] | ||||
def initialize_options(self): | ||||
pass | ||||
def finalize_options(self): | ||||
pass | ||||
|
r15027 | def run(self): | ||
print("bdist_wheel requires the wheel package") | ||||
|
r15034 | sys.exit(1) | ||
|
r15027 | if 'setuptools' not in sys.modules: | ||
return RequiresWheel | ||||
else: | ||||
try: | ||||
|
r15029 | from wheel.bdist_wheel import bdist_wheel, read_pkg_info, write_pkg_info | ||
|
r15027 | except ImportError: | ||
return RequiresWheel | ||||
|
r15029 | |||
|
r15027 | class bdist_wheel_tag(bdist_wheel): | ||
|
r15029 | |||
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) | ||||
|
r15598 | for pkg in ("gnureadline", "pyreadline", "mock"): | ||
|
r15029 | _remove_startswith(requires, pkg) | ||
|
r15414 | requires.append("gnureadline; sys.platform == 'darwin' and platform.python_implementation == 'CPython'") | ||
|
r16386 | requires.append("pyreadline (>=2.0); extra == 'terminal' and sys.platform == 'win32' and platform.python_implementation == 'CPython'") | ||
requires.append("pyreadline (>=2.0); extra == 'all' and sys.platform == 'win32' and platform.python_implementation == 'CPython'") | ||||
|
r15666 | requires.append("mock; extra == 'test' and python_version < '3.3'") | ||
|
r15029 | for r in requires: | ||
pkg_info['Requires-Dist'] = r | ||||
write_pkg_info(metadata_path, pkg_info) | ||||
|
r15027 | return bdist_wheel_tag | ||
#--------------------------------------------------------------------------- | ||||
|
r12531 | # Notebook related | ||
#--------------------------------------------------------------------------- | ||||
class CompileCSS(Command): | ||||
"""Recompile Notebook CSS | ||||
Regenerate the compiled CSS from LESS sources. | ||||
Requires various dev dependencies, such as fabric and lessc. | ||||
""" | ||||
description = "Recompile Notebook CSS" | ||||
|
r17333 | user_options = [ | ||
('minify', 'x', "minify CSS"), | ||||
|
r17344 | ('force', 'f', "force recompilation of CSS"), | ||
] | ||||
|
r12531 | |||
def initialize_options(self): | ||||
|
r17333 | self.minify = False | ||
|
r17344 | self.force = False | ||
|
r12531 | |||
def finalize_options(self): | ||||
|
r17333 | self.minify = bool(self.minify) | ||
|
r17344 | self.force = bool(self.force) | ||
|
r12531 | |||
def run(self): | ||||
|
r17345 | check_call([ | ||
"fab", | ||||
"css:minify=%s,force=%s" % (self.minify, self.force), | ||||
], cwd=pjoin(repo_root, "IPython", "html"), | ||||
|
r17333 | ) | ||
|
r13536 | |||
class JavascriptVersion(Command): | ||||
"""write the javascript version to notebook javascript""" | ||||
description = "Write IPython version to javascript" | ||||
user_options = [] | ||||
def initialize_options(self): | ||||
pass | ||||
def finalize_options(self): | ||||
pass | ||||
def run(self): | ||||
nsfile = pjoin(repo_root, "IPython", "html", "static", "base", "js", "namespace.js") | ||||
with open(nsfile) as f: | ||||
lines = f.readlines() | ||||
with open(nsfile, 'w') as f: | ||||
for line in lines: | ||||
if line.startswith("IPython.version"): | ||||
line = 'IPython.version = "{0}";\n'.format(version) | ||||
f.write(line) | ||||
|
r17333 | |||
|
r17343 | def css_js_prerelease(command, strict=True): | ||
|
r17333 | """decorator for building js/minified css prior to a release""" | ||
class DecoratedCommand(command): | ||||
def run(self): | ||||
self.distribution.run_command('jsversion') | ||||
css = self.distribution.get_command_obj('css') | ||||
css.minify = True | ||||
|
r17343 | try: | ||
self.distribution.run_command('css') | ||||
except Exception as e: | ||||
if strict: | ||||
raise | ||||
else: | ||||
log.warn("Failed to build css sourcemaps: %s" % e) | ||||
|
r17333 | command.run(self) | ||
return DecoratedCommand | ||||