add `setup.py js`

from JupyterHub

- fetches node tools locally with npm
- runs bower
- setup.py css uses local less as well,
  so no need to `npm install -g` anything.
Min RK 11 years ago
parent cfd1ee51e5
commit f9cb268e0d

@ -53,11 +53,8 @@ from setupbase import (
find_packages,
find_package_data,
check_package_data_first,
check_submodule_status,
require_submodules,
update_submodules,
UpdateSubmodules,
CompileCSS,
Bower,
JavascriptVersion,
css_js_prerelease,
)
@ -91,42 +88,6 @@ setup_args = dict(
)
#-------------------------------------------------------------------------------
# Make sure we aren't trying to run without submodules
#-------------------------------------------------------------------------------
here = os.path.abspath(os.path.dirname(__file__))
def require_clean_submodules():
"""Check on git submodules before distutils can do anything
Since distutils cannot be trusted to update the tree
after everything has been set in motion,
this is not a distutils command.
"""
# PACKAGERS: Add a return here to skip checks for git submodules
# don't do anything if nothing is actually supposed to happen
for do_nothing in ('-h', '--help', '--help-commands', 'clean', 'submodule'):
if do_nothing in sys.argv:
return
status = check_submodule_status(here)
if status == "missing":
print("checking out submodules for the first time")
update_submodules(here)
elif status == "unclean":
print('\n'.join([
"Cannot build / install with unclean submodules",
"Please update submodules with",
" python setup.py submodule",
"or",
" git submodule update",
"or commit any submodule changes you have made."
]))
sys.exit(1)
require_clean_submodules()
#---------------------------------------------------------------------------
# Find all the packages, package data, and data_files
@ -147,8 +108,8 @@ setup_args['cmdclass'] = {
'build_py': css_js_prerelease(
check_package_data_first(build_py)),
'sdist' : css_js_prerelease(sdist),
'submodule' : UpdateSubmodules,
'css' : CompileCSS,
'js' : Bower,
'jsversion' : JavascriptVersion,
}
@ -198,7 +159,9 @@ extras_require = {
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']['develop'] = css_js_prerelease(develop)
if not PY3:
setup_args['setup_requires'] = ['ipython_genutils']
try:
from wheel.bdist_wheel import bdist_wheel

@ -22,7 +22,7 @@ from distutils.cmd import Command
from distutils.errors import DistutilsExecError
from fnmatch import fnmatch
from glob import glob
from subprocess import Popen, PIPE
from subprocess import Popen, PIPE, check_call
#-------------------------------------------------------------------------------
# Useful globals and utility functions
@ -175,56 +175,86 @@ def check_package_data_first(command):
command.run(self)
return DecoratedCommand
#---------------------------------------------------------------------------
# VCS related
# Notebook related
#---------------------------------------------------------------------------
# utils.submodule has checks for submodule status
submodule = {}
execfile(pjoin('jupyter_notebook','submodule.py'), submodule)
check_submodule_status = submodule['check_submodule_status']
update_submodules = submodule['update_submodules']
try:
from shutil import which
except ImportError:
from ipython_genutils import which
static = pjoin(repo_root, 'jupyter_notebook', 'static')
npm_path = os.pathsep.join([
pjoin(repo_root, 'node_modules', '.bin'),
os.environ.get("PATH", os.defpath),
])
class UpdateSubmodules(Command):
"""Update git submodules
def mtime(path):
"""shorthand for mtime"""
return os.stat(path).st_mtime
py3compat_ns = {}
class Bower(Command):
description = "fetch static client-side components with bower"
The Notebook's external javascript dependencies live in a separate repo.
"""
description = "Update git submodules"
user_options = []
user_options = [
('force', 'f', "force fetching of bower dependencies"),
]
def initialize_options(self):
pass
self.force = False
def finalize_options(self):
pass
self.force = bool(self.force)
bower_dir = pjoin(static, 'components')
node_modules = pjoin(repo_root, 'node_modules')
def should_run(self):
if self.force:
return True
if not os.path.exists(self.bower_dir):
return True
return mtime(self.bower_dir) < mtime(pjoin(repo_root, 'bower.json'))
def should_run_npm(self):
if not which('npm'):
print("npm unavailable", file=sys.stderr)
return False
if not os.path.exists(self.node_modules):
return True
return mtime(self.node_modules) < mtime(pjoin(repo_root, 'package.json'))
def run(self):
try:
self.spawn('git submodule init'.split())
self.spawn('git submodule update --recursive'.split())
except Exception as e:
print(e)
if not self.should_run():
print("bower dependencies up to date")
return
if not check_submodule_status(repo_root) == 'clean':
print("submodules could not be checked out")
sys.exit(1)
def require_submodules(command):
"""decorator for instructing a command to check for submodules before running"""
class DecoratedCommand(command):
def run(self):
if not check_submodule_status(repo_root) == 'clean':
print("submodules missing! Run `setup.py submodule` and try again")
sys.exit(1)
command.run(self)
return DecoratedCommand
if self.should_run_npm():
print("installing build dependencies with npm")
check_call(['npm', 'install'], cwd=repo_root)
os.utime(self.node_modules)
env = os.environ.copy()
env['PATH'] = npm_path
try:
check_call(
['bower', 'install', '--allow-root', '--config.interactive=false'],
cwd=repo_root,
env=env,
)
except OSError as e:
print("Failed to run bower: %s" % e, file=sys.stderr)
print("You can install js dependencies with `npm install`", file=sys.stderr)
raise
os.utime(self.bower_dir)
# update package data in case this created new files
self.distribution.package_data = find_package_data()
#---------------------------------------------------------------------------
# Notebook related
#---------------------------------------------------------------------------
class CompileCSS(Command):
"""Recompile Notebook CSS
@ -235,33 +265,65 @@ class CompileCSS(Command):
"""
description = "Recompile Notebook CSS"
user_options = [
('minify', 'x', "minify CSS"),
('force', 'f', "force recompilation of CSS"),
]
def initialize_options(self):
self.minify = False
self.force = False
def finalize_options(self):
self.minify = bool(self.minify)
self.force = bool(self.force)
def run(self):
cmd = ['invoke', 'css']
if self.minify:
cmd.append('--minify')
def should_run(self):
"""Does less need to run?"""
if self.force:
cmd.append('--force')
try:
p = Popen(cmd, cwd=pjoin(repo_root, "jupyter_notebook"), stderr=PIPE)
except OSError:
raise DistutilsExecError("invoke is required to rebuild css (pip install invoke)")
out, err = p.communicate()
if p.returncode:
if sys.version_info[0] >= 3:
err = err.decode('utf8', 'replace')
raise DistutilsExecError(err.strip())
return True
css_targets = [pjoin(static, 'css', '%s.min.css' % name) for name in ('ipython', 'style')]
css_maps = [t + '.map' for t in css_targets]
targets = css_targets + css_maps
if not all(os.path.exists(t) for t in targets):
# some generated files don't exist
return True
earliest_target = sorted(mtime(t) for t in targets)[0]
# check if any .less files are newer than the generated targets
for (dirpath, dirnames, filenames) in os.walk(static):
for f in filenames:
if f.endswith('.less'):
path = pjoin(static, dirpath, f)
timestamp = mtime(path)
if timestamp > earliest_target:
return True
return False
def run(self):
if not self.should_run():
print("CSS up-to-date")
return
self.run_command('js')
env = os.environ.copy()
env['PATH'] = npm_path
for name in ('ipython', 'style'):
less = pjoin(static, 'style', '%s.less' % name)
css = pjoin(static, 'style', '%s.min.css' % name)
sourcemap = css + '.map'
try:
check_call([
'lessc', '--clean-css',
'--source-map-basepath={}'.format(static),
'--source-map={}'.format(sourcemap),
'--source-map-rootpath=../',
less, css,
], cwd=repo_root, env=env)
except OSError as e:
print("Failed to run lessc: %s" % e, file=sys.stderr)
print("You can install js dependencies with `npm install`", file=sys.stderr)
raise
# update package data in case this created new files
self.distribution.package_data = find_package_data()
class JavascriptVersion(Command):
@ -291,12 +353,14 @@ class JavascriptVersion(Command):
def css_js_prerelease(command):
"""decorator for building js/minified css prior to a release"""
"""decorator for building js/minified css prior to another command"""
class DecoratedCommand(command):
def run(self):
self.distribution.run_command('jsversion')
js = self.distribution.get_command_obj('js')
js.force = True
css = self.distribution.get_command_obj('css')
css.minify = True
css.force = True
try:
self.distribution.run_command('css')
except Exception as e:

Loading…
Cancel
Save