diff --git a/IPython/html/tasks.py b/IPython/html/tasks.py
index 4daf9f4..1eda695 100644
--- a/IPython/html/tasks.py
+++ b/IPython/html/tasks.py
@@ -1,10 +1,16 @@
"""invoke task file to build CSS"""
-from invoke import task, run
+from __future__ import print_function
+
import os
+import sys
from distutils.version import LooseVersion as V
from subprocess import check_output
+from invoke import task, run
+from invoke.runner import Result
+from invoke.exceptions import Failure
+
pjoin = os.path.join
static_dir = 'static'
components_dir = pjoin(static_dir, 'components')
@@ -13,6 +19,18 @@ here = os.path.dirname(__file__)
min_less_version = '2.0'
max_less_version = '3.0' # exclusive if string
+
+def _fail(msg=''):
+ """Fail a task, logging a message to stderr
+
+ raises a special Failure Exception from invoke.
+ """
+ if msg:
+ print(msg, file=sys.stderr)
+ # raising a Failure allows us to avoid a traceback
+ # we only care about exited, but stdout, stderr, pty are required args
+ raise Failure(Result(stdout='', stderr='', pty=False, exited=1))
+
def _need_css_update():
"""Does less need to run?"""
@@ -53,23 +71,30 @@ def css(minify=False, verbose=False, force=False):
sourcemap = pjoin('style', "%s.min.css.map" % name)
_compile_less(source, target, sourcemap, minify, verbose)
+
def _compile_less(source, target, sourcemap, minify=True, verbose=False):
"""Compile a less file by source and target relative to static_dir"""
min_flag = '-x' if minify else ''
ver_flag = '--verbose' if verbose else ''
+ install = "(npm install -g 'less@<{}')".format(max_less_version)
# pin less to version number from above
try:
out = check_output(['lessc', '--version'])
except OSError as err:
- raise ValueError("Unable to find lessc. Please install lessc >= %s and < %s " \
- % (min_less_version, max_less_version))
+ _fail("Unable to find lessc. Rebuilding css requires less >= {0} and < {1} {2}".format(
+ min_less_version, max_less_version, install
+ ))
out = out.decode('utf8', 'replace')
less_version = out.split()[1]
if min_less_version and V(less_version) < V(min_less_version):
- raise ValueError("lessc too old: %s < %s. Use `$ npm install less@X.Y.Z` to install a specific version of less" % (less_version, min_less_version))
+ _fail("lessc too old: {} < {} {}".format(
+ less_version, min_less_version, install,
+ ))
if max_less_version and V(less_version) >= V(max_less_version):
- raise ValueError("lessc too new: %s >= %s. Use `$ npm install less@X.Y.Z` to install a specific version of less" % (less_version, max_less_version))
+ _fail("lessc too new: {} >= {} {}".format(
+ less_version, max_less_version, install,
+ ))
static_path = pjoin(here, static_dir)
cwd = os.getcwd()
diff --git a/setupbase.py b/setupbase.py
index d32e413..a4e0d98 100644
--- a/setupbase.py
+++ b/setupbase.py
@@ -24,9 +24,10 @@ 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 distutils.errors import DistutilsExecError
from fnmatch import fnmatch
from glob import glob
-from subprocess import check_call
+from subprocess import Popen, PIPE
from setupext import install_data_ext
@@ -712,7 +713,15 @@ class CompileCSS(Command):
cmd.append('--minify')
if self.force:
cmd.append('--force')
- check_call(cmd, cwd=pjoin(repo_root, "IPython", "html"))
+ try:
+ p = Popen(cmd, cwd=pjoin(repo_root, "IPython", "html"), 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())
class JavascriptVersion(Command):
@@ -750,6 +759,7 @@ def css_js_prerelease(command, strict=True):
if strict:
raise
else:
- log.warn("Failed to build css sourcemaps: %s" % e)
+ log.warn("rebuilding css and sourcemaps failed (not a problem)")
+ log.warn(str(e))
command.run(self)
return DecoratedCommand