2008-06-07 06:24:37 +08:00
|
|
|
"""
|
2015-04-11 04:20:40 +08:00
|
|
|
This module defines the things that are used in setup.py for building the notebook
|
2008-06-07 06:24:37 +08:00
|
|
|
|
|
|
|
This includes:
|
|
|
|
|
|
|
|
* Functions for finding things like packages, package data, etc.
|
|
|
|
* A function for checking dependencies.
|
|
|
|
"""
|
2008-06-07 06:16:36 +08:00
|
|
|
|
2014-04-10 06:52:59 +08:00
|
|
|
# Copyright (c) IPython Development Team.
|
|
|
|
# Distributed under the terms of the Modified BSD License.
|
|
|
|
|
2010-10-27 08:57:12 +08:00
|
|
|
import os
|
2017-03-17 08:00:33 +08:00
|
|
|
import re
|
2017-01-04 20:20:13 +08:00
|
|
|
import pipes
|
|
|
|
import shutil
|
2010-10-27 08:57:12 +08:00
|
|
|
import sys
|
2008-06-07 06:16:36 +08:00
|
|
|
|
2014-07-24 06:33:08 +08:00
|
|
|
from distutils import log
|
2013-05-01 07:18:52 +08:00
|
|
|
from distutils.cmd import Command
|
2014-03-15 02:21:38 +08:00
|
|
|
from fnmatch import fnmatch
|
2008-06-07 06:16:36 +08:00
|
|
|
from glob import glob
|
2016-12-23 01:44:46 +08:00
|
|
|
from multiprocessing.pool import ThreadPool
|
|
|
|
from subprocess import check_call
|
2008-06-07 06:16:36 +08:00
|
|
|
|
2015-10-08 17:35:35 +08:00
|
|
|
if sys.platform == 'win32':
|
|
|
|
from subprocess import list2cmdline
|
|
|
|
else:
|
|
|
|
def list2cmdline(cmd_list):
|
|
|
|
return ' '.join(map(pipes.quote, cmd_list))
|
2015-07-22 07:34:44 +08:00
|
|
|
|
2022-07-25 20:36:46 +08:00
|
|
|
|
2008-06-07 06:16:36 +08:00
|
|
|
#-------------------------------------------------------------------------------
|
|
|
|
# Useful globals and utility functions
|
|
|
|
#-------------------------------------------------------------------------------
|
|
|
|
|
|
|
|
# A few handy globals
|
|
|
|
isfile = os.path.isfile
|
|
|
|
pjoin = os.path.join
|
2013-05-05 04:38:32 +08:00
|
|
|
repo_root = os.path.dirname(os.path.abspath(__file__))
|
2015-07-27 03:17:02 +08:00
|
|
|
is_repo = os.path.isdir(pjoin(repo_root, '.git'))
|
2008-06-07 06:16:36 +08:00
|
|
|
|
|
|
|
def oscmd(s):
|
2010-10-27 08:57:12 +08:00
|
|
|
print(">", s)
|
2008-06-07 06:16:36 +08:00
|
|
|
os.system(s)
|
2011-10-04 22:14:41 +08:00
|
|
|
|
2012-04-14 16:26:34 +08:00
|
|
|
# Py3 compatibility hacks, without assuming IPython itself is installed with
|
|
|
|
# the full py3compat machinery.
|
|
|
|
|
2011-08-05 05:32:15 +08:00
|
|
|
try:
|
|
|
|
execfile
|
|
|
|
except NameError:
|
|
|
|
def execfile(fname, globs, locs=None):
|
|
|
|
locs = locs or globs
|
|
|
|
exec(compile(open(fname).read(), fname, "exec"), globs, locs)
|
2008-06-07 06:16:36 +08:00
|
|
|
|
|
|
|
|
|
|
|
#---------------------------------------------------------------------------
|
|
|
|
# Basic project information
|
|
|
|
#---------------------------------------------------------------------------
|
|
|
|
|
2015-05-14 01:56:32 +08:00
|
|
|
name = 'notebook'
|
2015-04-11 04:20:40 +08:00
|
|
|
|
2009-08-04 23:22:09 +08:00
|
|
|
# release.py contains version, authors, license, url, keywords, etc.
|
2015-04-11 04:20:40 +08:00
|
|
|
version_ns = {}
|
|
|
|
execfile(pjoin(repo_root, name, '_version.py'), version_ns)
|
|
|
|
|
|
|
|
version = version_ns['__version__']
|
2008-06-07 06:16:36 +08:00
|
|
|
|
|
|
|
|
2017-03-17 08:00:33 +08:00
|
|
|
# vendored from pep440 package, we allow `.dev` suffix without trailing number.
|
|
|
|
loose_pep440re = re.compile(r'^([1-9]\d*!)?(0|[1-9]\d*)(\.(0|[1-9]\d*))*((a|b|rc)(0|[1-9]\d*))?(\.post(0|[1-9]\d*))?(\.dev(0|[1-9]\d*)?)?$')
|
|
|
|
if not loose_pep440re.match(version):
|
|
|
|
raise ValueError('Invalid version number `%s`, please follow pep440 convention or pip will get confused about which package is more recent.' % version)
|
|
|
|
|
2022-07-25 20:36:46 +08:00
|
|
|
|
2008-06-07 06:16:36 +08:00
|
|
|
#---------------------------------------------------------------------------
|
|
|
|
# Find packages
|
|
|
|
#---------------------------------------------------------------------------
|
|
|
|
|
|
|
|
def find_packages():
|
2008-06-07 06:24:37 +08:00
|
|
|
"""
|
2015-04-11 04:20:40 +08:00
|
|
|
Find all of the packages.
|
2008-06-07 06:24:37 +08:00
|
|
|
"""
|
2011-07-31 05:16:38 +08:00
|
|
|
packages = []
|
2015-04-11 04:20:40 +08:00
|
|
|
for dir,subdirs,files in os.walk(name):
|
2011-07-31 05:16:38 +08:00
|
|
|
package = dir.replace(os.path.sep, '.')
|
|
|
|
if '__init__.py' not in files:
|
|
|
|
# not a package
|
|
|
|
continue
|
|
|
|
packages.append(package)
|
2008-06-07 06:16:36 +08:00
|
|
|
return packages
|
|
|
|
|
2022-07-25 20:36:46 +08:00
|
|
|
|
2008-06-07 06:16:36 +08:00
|
|
|
#---------------------------------------------------------------------------
|
|
|
|
# Find package data
|
|
|
|
#---------------------------------------------------------------------------
|
|
|
|
|
|
|
|
def find_package_data():
|
2008-06-07 06:24:37 +08:00
|
|
|
"""
|
2015-04-11 04:20:40 +08:00
|
|
|
Find package_data.
|
2008-06-07 06:24:37 +08:00
|
|
|
"""
|
2017-11-10 18:30:54 +08:00
|
|
|
# This is not enough for these things to appear in a sdist.
|
2008-06-07 06:16:36 +08:00
|
|
|
# We need to muck with the MANIFEST to get this to work
|
2022-04-11 20:01:43 +08:00
|
|
|
|
2015-04-11 04:20:40 +08:00
|
|
|
# walk notebook resources:
|
|
|
|
cwd = os.getcwd()
|
2015-05-14 01:56:32 +08:00
|
|
|
os.chdir('notebook')
|
2015-04-11 04:20:40 +08:00
|
|
|
|
|
|
|
os.chdir(cwd)
|
2013-09-27 08:39:06 +08:00
|
|
|
|
2008-07-02 12:19:24 +08:00
|
|
|
package_data = {
|
2022-07-25 20:36:46 +08:00
|
|
|
'notebook' : ['templates/*'],
|
2016-08-21 01:20:16 +08:00
|
|
|
'notebook.bundler.tests': ['resources/*', 'resources/*/*', 'resources/*/*/.*'],
|
2017-01-30 22:44:33 +08:00
|
|
|
'notebook.services.api': ['api.yaml'],
|
2017-11-01 02:40:16 +08:00
|
|
|
'notebook.i18n': ['*/LC_MESSAGES/*.*'],
|
2008-07-02 12:19:24 +08:00
|
|
|
}
|
2022-04-11 20:01:43 +08:00
|
|
|
|
2014-02-06 13:36:49 +08:00
|
|
|
return package_data
|
|
|
|
|
2014-02-08 06:12:53 +08:00
|
|
|
|
2014-02-06 13:36:49 +08:00
|
|
|
def check_package_data(package_data):
|
|
|
|
"""verify that package_data globs make sense"""
|
|
|
|
print("checking package data")
|
2014-02-01 06:42:40 +08:00
|
|
|
for pkg, data in package_data.items():
|
|
|
|
pkg_root = pjoin(*pkg.split('.'))
|
|
|
|
for d in data:
|
|
|
|
path = pjoin(pkg_root, d)
|
2016-06-03 02:14:05 +08:00
|
|
|
if '*' in path:
|
2014-02-01 06:42:40 +08:00
|
|
|
assert len(glob(path)) > 0, "No files match pattern %s" % path
|
|
|
|
else:
|
|
|
|
assert os.path.exists(path), "Missing package data: %s" % path
|
|
|
|
|
2008-06-07 06:16:36 +08:00
|
|
|
|
2014-02-08 06:12:53 +08:00
|
|
|
def check_package_data_first(command):
|
|
|
|
"""decorator for checking package_data before running a given command
|
2022-04-11 20:01:43 +08:00
|
|
|
|
2014-02-08 06:12:53 +08:00
|
|
|
Probably only needs to wrap build_py
|
|
|
|
"""
|
|
|
|
class DecoratedCommand(command):
|
|
|
|
def run(self):
|
|
|
|
check_package_data(self.package_data)
|
|
|
|
command.run(self)
|
|
|
|
return DecoratedCommand
|
2008-06-07 06:16:36 +08:00
|
|
|
|
2022-07-25 20:36:46 +08:00
|
|
|
|
2015-04-23 07:10:46 +08:00
|
|
|
def update_package_data(distribution):
|
|
|
|
"""update package_data to catch changes during setup"""
|
|
|
|
build_py = distribution.get_command_obj('build_py')
|
|
|
|
distribution.package_data = find_package_data()
|
|
|
|
# re-init build_py options which load package_data
|
|
|
|
build_py.finalize_options()
|
|
|
|
|
2022-07-25 20:36:46 +08:00
|
|
|
|
2013-05-01 07:18:52 +08:00
|
|
|
#---------------------------------------------------------------------------
|
2015-04-12 07:19:00 +08:00
|
|
|
# Notebook related
|
2013-05-01 07:18:52 +08:00
|
|
|
#---------------------------------------------------------------------------
|
|
|
|
|
2015-04-12 07:19:00 +08:00
|
|
|
try:
|
|
|
|
from shutil import which
|
|
|
|
except ImportError:
|
2015-04-13 22:09:05 +08:00
|
|
|
## which() function copied from Python 3.4.3; PSF license
|
2015-04-13 22:04:25 +08:00
|
|
|
def which(cmd, mode=os.F_OK | os.X_OK, path=None):
|
|
|
|
"""Given a command, mode, and a PATH string, return the path which
|
|
|
|
conforms to the given mode on the PATH, or None if there is no such
|
|
|
|
file.
|
|
|
|
|
|
|
|
`mode` defaults to os.F_OK | os.X_OK. `path` defaults to the result
|
|
|
|
of os.environ.get("PATH"), or can be overridden with a custom search
|
|
|
|
path.
|
|
|
|
|
|
|
|
"""
|
|
|
|
# Check that a given file can be accessed with the correct mode.
|
|
|
|
# Additionally check that `file` is not a directory, as on Windows
|
|
|
|
# directories pass the os.access check.
|
|
|
|
def _access_check(fn, mode):
|
|
|
|
return (os.path.exists(fn) and os.access(fn, mode)
|
|
|
|
and not os.path.isdir(fn))
|
|
|
|
|
|
|
|
# If we're given a path with a directory part, look it up directly rather
|
|
|
|
# than referring to PATH directories. This includes checking relative to the
|
|
|
|
# current directory, e.g. ./script
|
|
|
|
if os.path.dirname(cmd):
|
|
|
|
if _access_check(cmd, mode):
|
|
|
|
return cmd
|
|
|
|
return None
|
|
|
|
|
|
|
|
if path is None:
|
|
|
|
path = os.environ.get("PATH", os.defpath)
|
|
|
|
if not path:
|
|
|
|
return None
|
|
|
|
path = path.split(os.pathsep)
|
|
|
|
|
|
|
|
if sys.platform == "win32":
|
|
|
|
# The current directory takes precedence on Windows.
|
|
|
|
if not os.curdir in path:
|
|
|
|
path.insert(0, os.curdir)
|
|
|
|
|
|
|
|
# PATHEXT is necessary to check on Windows.
|
|
|
|
pathext = os.environ.get("PATHEXT", "").split(os.pathsep)
|
|
|
|
# See if the given file matches any of the expected path extensions.
|
|
|
|
# This will allow us to short circuit when given "python.exe".
|
|
|
|
# If it does match, only test that one, otherwise we have to try
|
|
|
|
# others.
|
|
|
|
if any(cmd.lower().endswith(ext.lower()) for ext in pathext):
|
|
|
|
files = [cmd]
|
|
|
|
else:
|
|
|
|
files = [cmd + ext for ext in pathext]
|
|
|
|
else:
|
|
|
|
# On other platforms you don't have things like PATHEXT to tell you
|
|
|
|
# what file suffixes are executable, so just pass on cmd as-is.
|
|
|
|
files = [cmd]
|
|
|
|
|
|
|
|
seen = set()
|
|
|
|
for dir in path:
|
|
|
|
normdir = os.path.normcase(dir)
|
|
|
|
if not normdir in seen:
|
|
|
|
seen.add(normdir)
|
|
|
|
for thefile in files:
|
|
|
|
name = os.path.join(dir, thefile)
|
|
|
|
if _access_check(name, mode):
|
|
|
|
return name
|
|
|
|
return None
|
|
|
|
|
2015-04-12 07:19:00 +08:00
|
|
|
|
2015-05-14 01:56:32 +08:00
|
|
|
static = pjoin(repo_root, 'notebook', 'static')
|
2015-04-12 07:19:00 +08:00
|
|
|
|
2013-05-01 07:18:52 +08:00
|
|
|
|
2015-04-12 07:19:00 +08:00
|
|
|
def mtime(path):
|
|
|
|
"""shorthand for mtime"""
|
|
|
|
return os.stat(path).st_mtime
|
|
|
|
|
2015-04-23 07:10:46 +08:00
|
|
|
|
2015-07-22 07:34:44 +08:00
|
|
|
def run(cmd, *args, **kwargs):
|
|
|
|
"""Echo a command before running it"""
|
2015-10-08 17:35:35 +08:00
|
|
|
log.info('> ' + list2cmdline(cmd))
|
|
|
|
kwargs['shell'] = (sys.platform == 'win32')
|
2015-07-22 07:34:44 +08:00
|
|
|
return check_call(cmd, *args, **kwargs)
|
|
|
|
|
2022-07-25 20:36:46 +08:00
|
|
|
|
2018-09-16 04:01:20 +08:00
|
|
|
class CompileBackendTranslation(Command):
|
|
|
|
description = "compile the .po files into .mo files, that contain the translations."
|
|
|
|
|
|
|
|
user_options = []
|
|
|
|
|
|
|
|
def initialize_options(self):
|
|
|
|
pass
|
|
|
|
|
|
|
|
def finalize_options(self):
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
def run(self):
|
|
|
|
paths = glob('notebook/i18n/??_??')
|
|
|
|
for p in paths:
|
2018-09-17 00:12:41 +08:00
|
|
|
LANG = p[-5:]
|
2018-09-16 04:01:20 +08:00
|
|
|
for component in ['notebook', 'nbui']:
|
|
|
|
run(['pybabel', 'compile',
|
|
|
|
'-D', component,
|
|
|
|
'-f',
|
|
|
|
'-l', LANG,
|
|
|
|
'-i', pjoin('notebook', 'i18n', LANG, 'LC_MESSAGES', component+'.po'),
|
|
|
|
'-o', pjoin('notebook', 'i18n', LANG, 'LC_MESSAGES', component+'.mo')
|
|
|
|
])
|
2015-04-12 07:19:00 +08:00
|
|
|
|
2013-09-15 03:21:05 +08:00
|
|
|
|
2018-01-16 00:59:01 +08:00
|
|
|
def patch_out_bootstrap_bw_print():
|
|
|
|
"""Hack! Manually patch out the bootstrap rule that forces printing in B&W.
|
|
|
|
|
|
|
|
We haven't found a way to override this rule with another one.
|
|
|
|
"""
|
|
|
|
print_less = pjoin(static, 'components', 'bootstrap', 'less', 'print.less')
|
|
|
|
with open(print_less) as f:
|
|
|
|
lines = f.readlines()
|
|
|
|
|
|
|
|
for ix, line in enumerate(lines):
|
|
|
|
if 'Black prints faster' in line:
|
|
|
|
break
|
|
|
|
else:
|
|
|
|
return # Already patched out, nothing to do.
|
|
|
|
|
|
|
|
rmed = lines.pop(ix)
|
|
|
|
print("Removed line", ix, "from bootstrap print.less:")
|
|
|
|
print("-", rmed)
|
|
|
|
print()
|
|
|
|
with open(print_less, 'w') as f:
|
|
|
|
f.writelines(lines)
|
|
|
|
|