##// END OF EJS Templates
Merge pull request #4706 from minrk/irunner...
Thomas Kluyver -
r13859:9a88a54b merge
parent child Browse files
Show More
@@ -0,0 +1,2 b''
1 :mod:`IPython.lib.irunner` and its command-line entry point have been removed.
2 It had fallen out of use long ago. No newline at end of file
@@ -1,15 +1,11 b''
1 1 # encoding: utf-8
2 2 from IPython.testing import decorators as dec
3 3
4 4 def test_import_backgroundjobs():
5 5 from IPython.lib import backgroundjobs
6 6
7 7 def test_import_deepreload():
8 8 from IPython.lib import deepreload
9 9
10 10 def test_import_demo():
11 11 from IPython.lib import demo
12
13 @dec.skip_win32
14 def test_import_irunner():
15 from IPython.lib import irunner
@@ -1,530 +1,525 b''
1 1 # -*- coding: utf-8 -*-
2 2 """IPython Test Suite Runner.
3 3
4 4 This module provides a main entry point to a user script to test IPython
5 5 itself from the command line. There are two ways of running this script:
6 6
7 7 1. With the syntax `iptest all`. This runs our entire test suite by
8 8 calling this script (with different arguments) recursively. This
9 9 causes modules and package to be tested in different processes, using nose
10 10 or trial where appropriate.
11 11 2. With the regular nose syntax, like `iptest -vvs IPython`. In this form
12 12 the script simply calls nose, but with special command line flags and
13 13 plugins loaded.
14 14
15 15 """
16 16
17 17 #-----------------------------------------------------------------------------
18 18 # Copyright (C) 2009-2011 The IPython Development Team
19 19 #
20 20 # Distributed under the terms of the BSD License. The full license is in
21 21 # the file COPYING, distributed as part of this software.
22 22 #-----------------------------------------------------------------------------
23 23
24 24 #-----------------------------------------------------------------------------
25 25 # Imports
26 26 #-----------------------------------------------------------------------------
27 27 from __future__ import print_function
28 28
29 29 # Stdlib
30 30 import glob
31 31 from io import BytesIO
32 32 import os
33 33 import os.path as path
34 34 import sys
35 35 from threading import Thread, Lock, Event
36 36 import warnings
37 37
38 38 # Now, proceed to import nose itself
39 39 import nose.plugins.builtin
40 40 from nose.plugins.xunit import Xunit
41 41 from nose import SkipTest
42 42 from nose.core import TestProgram
43 43 from nose.plugins import Plugin
44 44 from nose.util import safe_str
45 45
46 46 # Our own imports
47 47 from IPython.utils.process import is_cmd_found
48 48 from IPython.utils.importstring import import_item
49 49 from IPython.testing.plugin.ipdoctest import IPythonDoctest
50 50 from IPython.external.decorators import KnownFailure, knownfailureif
51 51
52 52 pjoin = path.join
53 53
54 54
55 55 #-----------------------------------------------------------------------------
56 56 # Globals
57 57 #-----------------------------------------------------------------------------
58 58
59 59
60 60 #-----------------------------------------------------------------------------
61 61 # Warnings control
62 62 #-----------------------------------------------------------------------------
63 63
64 64 # Twisted generates annoying warnings with Python 2.6, as will do other code
65 65 # that imports 'sets' as of today
66 66 warnings.filterwarnings('ignore', 'the sets module is deprecated',
67 67 DeprecationWarning )
68 68
69 69 # This one also comes from Twisted
70 70 warnings.filterwarnings('ignore', 'the sha module is deprecated',
71 71 DeprecationWarning)
72 72
73 73 # Wx on Fedora11 spits these out
74 74 warnings.filterwarnings('ignore', 'wxPython/wxWidgets release number mismatch',
75 75 UserWarning)
76 76
77 77 # ------------------------------------------------------------------------------
78 78 # Monkeypatch Xunit to count known failures as skipped.
79 79 # ------------------------------------------------------------------------------
80 80 def monkeypatch_xunit():
81 81 try:
82 82 knownfailureif(True)(lambda: None)()
83 83 except Exception as e:
84 84 KnownFailureTest = type(e)
85 85
86 86 def addError(self, test, err, capt=None):
87 87 if issubclass(err[0], KnownFailureTest):
88 88 err = (SkipTest,) + err[1:]
89 89 return self.orig_addError(test, err, capt)
90 90
91 91 Xunit.orig_addError = Xunit.addError
92 92 Xunit.addError = addError
93 93
94 94 #-----------------------------------------------------------------------------
95 95 # Check which dependencies are installed and greater than minimum version.
96 96 #-----------------------------------------------------------------------------
97 97 def extract_version(mod):
98 98 return mod.__version__
99 99
100 100 def test_for(item, min_version=None, callback=extract_version):
101 101 """Test to see if item is importable, and optionally check against a minimum
102 102 version.
103 103
104 104 If min_version is given, the default behavior is to check against the
105 105 `__version__` attribute of the item, but specifying `callback` allows you to
106 106 extract the value you are interested in. e.g::
107 107
108 108 In [1]: import sys
109 109
110 110 In [2]: from IPython.testing.iptest import test_for
111 111
112 112 In [3]: test_for('sys', (2,6), callback=lambda sys: sys.version_info)
113 113 Out[3]: True
114 114
115 115 """
116 116 try:
117 117 check = import_item(item)
118 118 except (ImportError, RuntimeError):
119 119 # GTK reports Runtime error if it can't be initialized even if it's
120 120 # importable.
121 121 return False
122 122 else:
123 123 if min_version:
124 124 if callback:
125 125 # extra processing step to get version to compare
126 126 check = callback(check)
127 127
128 128 return check >= min_version
129 129 else:
130 130 return True
131 131
132 132 # Global dict where we can store information on what we have and what we don't
133 133 # have available at test run time
134 134 have = {}
135 135
136 136 have['curses'] = test_for('_curses')
137 137 have['matplotlib'] = test_for('matplotlib')
138 138 have['numpy'] = test_for('numpy')
139 139 have['pexpect'] = test_for('IPython.external.pexpect')
140 140 have['pymongo'] = test_for('pymongo')
141 141 have['pygments'] = test_for('pygments')
142 142 have['qt'] = test_for('IPython.external.qt')
143 143 have['rpy2'] = test_for('rpy2')
144 144 have['sqlite3'] = test_for('sqlite3')
145 145 have['cython'] = test_for('Cython')
146 146 have['oct2py'] = test_for('oct2py')
147 147 have['tornado'] = test_for('tornado.version_info', (3,1,0), callback=None)
148 148 have['jinja2'] = test_for('jinja2')
149 149 have['azure'] = test_for('azure')
150 150 have['requests'] = test_for('requests')
151 151 have['sphinx'] = test_for('sphinx')
152 152 have['casperjs'] = is_cmd_found('casperjs')
153 153
154 154 min_zmq = (2,1,11)
155 155
156 156 have['zmq'] = test_for('zmq.pyzmq_version_info', min_zmq, callback=lambda x: x())
157 157
158 158 #-----------------------------------------------------------------------------
159 159 # Test suite definitions
160 160 #-----------------------------------------------------------------------------
161 161
162 162 test_group_names = ['parallel', 'kernel', 'kernel.inprocess', 'config', 'core',
163 163 'extensions', 'lib', 'terminal', 'testing', 'utils',
164 164 'nbformat', 'qt', 'html', 'nbconvert'
165 165 ]
166 166
167 167 class TestSection(object):
168 168 def __init__(self, name, includes):
169 169 self.name = name
170 170 self.includes = includes
171 171 self.excludes = []
172 172 self.dependencies = []
173 173 self.enabled = True
174 174
175 175 def exclude(self, module):
176 176 if not module.startswith('IPython'):
177 177 module = self.includes[0] + "." + module
178 178 self.excludes.append(module.replace('.', os.sep))
179 179
180 180 def requires(self, *packages):
181 181 self.dependencies.extend(packages)
182 182
183 183 @property
184 184 def will_run(self):
185 185 return self.enabled and all(have[p] for p in self.dependencies)
186 186
187 187 # Name -> (include, exclude, dependencies_met)
188 188 test_sections = {n:TestSection(n, ['IPython.%s' % n]) for n in test_group_names}
189 189
190 190 # Exclusions and dependencies
191 191 # ---------------------------
192 192
193 193 # core:
194 194 sec = test_sections['core']
195 195 if not have['sqlite3']:
196 196 sec.exclude('tests.test_history')
197 197 sec.exclude('history')
198 198 if not have['matplotlib']:
199 199 sec.exclude('pylabtools'),
200 200 sec.exclude('tests.test_pylabtools')
201 201
202 202 # lib:
203 203 sec = test_sections['lib']
204 if not have['pexpect']:
205 sec.exclude('irunner')
206 sec.exclude('tests.test_irunner')
207 204 if not have['zmq']:
208 205 sec.exclude('kernel')
209 206 # We do this unconditionally, so that the test suite doesn't import
210 207 # gtk, changing the default encoding and masking some unicode bugs.
211 208 sec.exclude('inputhookgtk')
212 209 # We also do this unconditionally, because wx can interfere with Unix signals.
213 210 # There are currently no tests for it anyway.
214 211 sec.exclude('inputhookwx')
215 212 # Testing inputhook will need a lot of thought, to figure out
216 213 # how to have tests that don't lock up with the gui event
217 214 # loops in the picture
218 215 sec.exclude('inputhook')
219 216
220 217 # testing:
221 218 sec = test_sections['testing']
222 # This guy is probably attic material
223 sec.exclude('mkdoctests')
224 219 # These have to be skipped on win32 because they use echo, rm, cd, etc.
225 220 # See ticket https://github.com/ipython/ipython/issues/87
226 221 if sys.platform == 'win32':
227 222 sec.exclude('plugin.test_exampleip')
228 223 sec.exclude('plugin.dtexample')
229 224
230 225 # terminal:
231 226 if (not have['pexpect']) or (not have['zmq']):
232 227 test_sections['terminal'].exclude('console')
233 228
234 229 # parallel
235 230 sec = test_sections['parallel']
236 231 sec.requires('zmq')
237 232 if not have['pymongo']:
238 233 sec.exclude('controller.mongodb')
239 234 sec.exclude('tests.test_mongodb')
240 235
241 236 # kernel:
242 237 sec = test_sections['kernel']
243 238 sec.requires('zmq')
244 239 # The in-process kernel tests are done in a separate section
245 240 sec.exclude('inprocess')
246 241 # importing gtk sets the default encoding, which we want to avoid
247 242 sec.exclude('zmq.gui.gtkembed')
248 243 if not have['matplotlib']:
249 244 sec.exclude('zmq.pylab')
250 245
251 246 # kernel.inprocess:
252 247 test_sections['kernel.inprocess'].requires('zmq')
253 248
254 249 # extensions:
255 250 sec = test_sections['extensions']
256 251 if not have['cython']:
257 252 sec.exclude('cythonmagic')
258 253 sec.exclude('tests.test_cythonmagic')
259 254 if not have['oct2py']:
260 255 sec.exclude('octavemagic')
261 256 sec.exclude('tests.test_octavemagic')
262 257 if not have['rpy2'] or not have['numpy']:
263 258 sec.exclude('rmagic')
264 259 sec.exclude('tests.test_rmagic')
265 260 # autoreload does some strange stuff, so move it to its own test section
266 261 sec.exclude('autoreload')
267 262 sec.exclude('tests.test_autoreload')
268 263 test_sections['autoreload'] = TestSection('autoreload',
269 264 ['IPython.extensions.autoreload', 'IPython.extensions.tests.test_autoreload'])
270 265 test_group_names.append('autoreload')
271 266
272 267 # qt:
273 268 test_sections['qt'].requires('zmq', 'qt', 'pygments')
274 269
275 270 # html:
276 271 sec = test_sections['html']
277 272 sec.requires('zmq', 'tornado', 'requests')
278 273 # The notebook 'static' directory contains JS, css and other
279 274 # files for web serving. Occasionally projects may put a .py
280 275 # file in there (MathJax ships a conf.py), so we might as
281 276 # well play it safe and skip the whole thing.
282 277 sec.exclude('static')
283 278 sec.exclude('fabfile')
284 279 if not have['jinja2']:
285 280 sec.exclude('notebookapp')
286 281 if not have['azure']:
287 282 sec.exclude('services.notebooks.azurenbmanager')
288 283
289 284 # config:
290 285 # Config files aren't really importable stand-alone
291 286 test_sections['config'].exclude('profile')
292 287
293 288 # nbconvert:
294 289 sec = test_sections['nbconvert']
295 290 sec.requires('pygments', 'jinja2', 'sphinx')
296 291 # Exclude nbconvert directories containing config files used to test.
297 292 # Executing the config files with iptest would cause an exception.
298 293 sec.exclude('tests.files')
299 294 sec.exclude('exporters.tests.files')
300 295 if not have['tornado']:
301 296 sec.exclude('nbconvert.post_processors.serve')
302 297 sec.exclude('nbconvert.post_processors.tests.test_serve')
303 298
304 299 #-----------------------------------------------------------------------------
305 300 # Functions and classes
306 301 #-----------------------------------------------------------------------------
307 302
308 303 def check_exclusions_exist():
309 304 from IPython.utils.path import get_ipython_package_dir
310 305 from IPython.utils.warn import warn
311 306 parent = os.path.dirname(get_ipython_package_dir())
312 307 for sec in test_sections:
313 308 for pattern in sec.exclusions:
314 309 fullpath = pjoin(parent, pattern)
315 310 if not os.path.exists(fullpath) and not glob.glob(fullpath + '.*'):
316 311 warn("Excluding nonexistent file: %r" % pattern)
317 312
318 313
319 314 class ExclusionPlugin(Plugin):
320 315 """A nose plugin to effect our exclusions of files and directories.
321 316 """
322 317 name = 'exclusions'
323 318 score = 3000 # Should come before any other plugins
324 319
325 320 def __init__(self, exclude_patterns=None):
326 321 """
327 322 Parameters
328 323 ----------
329 324
330 325 exclude_patterns : sequence of strings, optional
331 326 Filenames containing these patterns (as raw strings, not as regular
332 327 expressions) are excluded from the tests.
333 328 """
334 329 self.exclude_patterns = exclude_patterns or []
335 330 super(ExclusionPlugin, self).__init__()
336 331
337 332 def options(self, parser, env=os.environ):
338 333 Plugin.options(self, parser, env)
339 334
340 335 def configure(self, options, config):
341 336 Plugin.configure(self, options, config)
342 337 # Override nose trying to disable plugin.
343 338 self.enabled = True
344 339
345 340 def wantFile(self, filename):
346 341 """Return whether the given filename should be scanned for tests.
347 342 """
348 343 if any(pat in filename for pat in self.exclude_patterns):
349 344 return False
350 345 return None
351 346
352 347 def wantDirectory(self, directory):
353 348 """Return whether the given directory should be scanned for tests.
354 349 """
355 350 if any(pat in directory for pat in self.exclude_patterns):
356 351 return False
357 352 return None
358 353
359 354
360 355 class StreamCapturer(Thread):
361 356 daemon = True # Don't hang if main thread crashes
362 357 started = False
363 358 def __init__(self):
364 359 super(StreamCapturer, self).__init__()
365 360 self.streams = []
366 361 self.buffer = BytesIO()
367 362 self.readfd, self.writefd = os.pipe()
368 363 self.buffer_lock = Lock()
369 364 self.stop = Event()
370 365
371 366 def run(self):
372 367 self.started = True
373 368
374 369 while not self.stop.is_set():
375 370 chunk = os.read(self.readfd, 1024)
376 371
377 372 with self.buffer_lock:
378 373 self.buffer.write(chunk)
379 374
380 375 os.close(self.readfd)
381 376 os.close(self.writefd)
382 377
383 378 def reset_buffer(self):
384 379 with self.buffer_lock:
385 380 self.buffer.truncate(0)
386 381 self.buffer.seek(0)
387 382
388 383 def get_buffer(self):
389 384 with self.buffer_lock:
390 385 return self.buffer.getvalue()
391 386
392 387 def ensure_started(self):
393 388 if not self.started:
394 389 self.start()
395 390
396 391 def halt(self):
397 392 """Safely stop the thread."""
398 393 if not self.started:
399 394 return
400 395
401 396 self.stop.set()
402 397 os.write(self.writefd, b'wake up') # Ensure we're not locked in a read()
403 398 self.join()
404 399
405 400 class SubprocessStreamCapturePlugin(Plugin):
406 401 name='subprocstreams'
407 402 def __init__(self):
408 403 Plugin.__init__(self)
409 404 self.stream_capturer = StreamCapturer()
410 405 self.destination = os.environ.get('IPTEST_SUBPROC_STREAMS', 'capture')
411 406 # This is ugly, but distant parts of the test machinery need to be able
412 407 # to redirect streams, so we make the object globally accessible.
413 408 nose.iptest_stdstreams_fileno = self.get_write_fileno
414 409
415 410 def get_write_fileno(self):
416 411 if self.destination == 'capture':
417 412 self.stream_capturer.ensure_started()
418 413 return self.stream_capturer.writefd
419 414 elif self.destination == 'discard':
420 415 return os.open(os.devnull, os.O_WRONLY)
421 416 else:
422 417 return sys.__stdout__.fileno()
423 418
424 419 def configure(self, options, config):
425 420 Plugin.configure(self, options, config)
426 421 # Override nose trying to disable plugin.
427 422 if self.destination == 'capture':
428 423 self.enabled = True
429 424
430 425 def startTest(self, test):
431 426 # Reset log capture
432 427 self.stream_capturer.reset_buffer()
433 428
434 429 def formatFailure(self, test, err):
435 430 # Show output
436 431 ec, ev, tb = err
437 432 captured = self.stream_capturer.get_buffer().decode('utf-8', 'replace')
438 433 if captured.strip():
439 434 ev = safe_str(ev)
440 435 out = [ev, '>> begin captured subprocess output <<',
441 436 captured,
442 437 '>> end captured subprocess output <<']
443 438 return ec, '\n'.join(out), tb
444 439
445 440 return err
446 441
447 442 formatError = formatFailure
448 443
449 444 def finalize(self, result):
450 445 self.stream_capturer.halt()
451 446
452 447
453 448 def run_iptest():
454 449 """Run the IPython test suite using nose.
455 450
456 451 This function is called when this script is **not** called with the form
457 452 `iptest all`. It simply calls nose with appropriate command line flags
458 453 and accepts all of the standard nose arguments.
459 454 """
460 455 # Apply our monkeypatch to Xunit
461 456 if '--with-xunit' in sys.argv and not hasattr(Xunit, 'orig_addError'):
462 457 monkeypatch_xunit()
463 458
464 459 warnings.filterwarnings('ignore',
465 460 'This will be removed soon. Use IPython.testing.util instead')
466 461
467 462 arg1 = sys.argv[1]
468 463 if arg1 in test_sections:
469 464 section = test_sections[arg1]
470 465 sys.argv[1:2] = section.includes
471 466 elif arg1.startswith('IPython.') and arg1[8:] in test_sections:
472 467 section = test_sections[arg1[8:]]
473 468 sys.argv[1:2] = section.includes
474 469 else:
475 470 section = TestSection(arg1, includes=[arg1])
476 471
477 472
478 473 argv = sys.argv + [ '--detailed-errors', # extra info in tracebacks
479 474
480 475 '--with-ipdoctest',
481 476 '--ipdoctest-tests','--ipdoctest-extension=txt',
482 477
483 478 # We add --exe because of setuptools' imbecility (it
484 479 # blindly does chmod +x on ALL files). Nose does the
485 480 # right thing and it tries to avoid executables,
486 481 # setuptools unfortunately forces our hand here. This
487 482 # has been discussed on the distutils list and the
488 483 # setuptools devs refuse to fix this problem!
489 484 '--exe',
490 485 ]
491 486 if '-a' not in argv and '-A' not in argv:
492 487 argv = argv + ['-a', '!crash']
493 488
494 489 if nose.__version__ >= '0.11':
495 490 # I don't fully understand why we need this one, but depending on what
496 491 # directory the test suite is run from, if we don't give it, 0 tests
497 492 # get run. Specifically, if the test suite is run from the source dir
498 493 # with an argument (like 'iptest.py IPython.core', 0 tests are run,
499 494 # even if the same call done in this directory works fine). It appears
500 495 # that if the requested package is in the current dir, nose bails early
501 496 # by default. Since it's otherwise harmless, leave it in by default
502 497 # for nose >= 0.11, though unfortunately nose 0.10 doesn't support it.
503 498 argv.append('--traverse-namespace')
504 499
505 500 # use our plugin for doctesting. It will remove the standard doctest plugin
506 501 # if it finds it enabled
507 502 plugins = [ExclusionPlugin(section.excludes), IPythonDoctest(), KnownFailure(),
508 503 SubprocessStreamCapturePlugin() ]
509 504
510 505 # Use working directory set by parent process (see iptestcontroller)
511 506 if 'IPTEST_WORKING_DIR' in os.environ:
512 507 os.chdir(os.environ['IPTEST_WORKING_DIR'])
513 508
514 509 # We need a global ipython running in this process, but the special
515 510 # in-process group spawns its own IPython kernels, so for *that* group we
516 511 # must avoid also opening the global one (otherwise there's a conflict of
517 512 # singletons). Ultimately the solution to this problem is to refactor our
518 513 # assumptions about what needs to be a singleton and what doesn't (app
519 514 # objects should, individual shells shouldn't). But for now, this
520 515 # workaround allows the test suite for the inprocess module to complete.
521 516 if 'kernel.inprocess' not in section.name:
522 517 from IPython.testing import globalipapp
523 518 globalipapp.start_ipython()
524 519
525 520 # Now nose can run
526 521 TestProgram(argv=argv, addplugins=plugins)
527 522
528 523 if __name__ == '__main__':
529 524 run_iptest()
530 525
@@ -1,589 +1,587 b''
1 1 # encoding: utf-8
2 2 """
3 3 This module defines the things that are used in setup.py for building IPython
4 4
5 5 This includes:
6 6
7 7 * The basic arguments to setup
8 8 * Functions for finding things like packages, package data, etc.
9 9 * A function for checking dependencies.
10 10 """
11 11 from __future__ import print_function
12 12
13 13 #-------------------------------------------------------------------------------
14 14 # Copyright (C) 2008 The IPython Development Team
15 15 #
16 16 # Distributed under the terms of the BSD License. The full license is in
17 17 # the file COPYING, distributed as part of this software.
18 18 #-------------------------------------------------------------------------------
19 19
20 20 #-------------------------------------------------------------------------------
21 21 # Imports
22 22 #-------------------------------------------------------------------------------
23 23 import errno
24 24 import os
25 25 import sys
26 26
27 27 from distutils.command.build_py import build_py
28 28 from distutils.command.build_scripts import build_scripts
29 29 from distutils.command.install import install
30 30 from distutils.command.install_scripts import install_scripts
31 31 from distutils.cmd import Command
32 32 from glob import glob
33 33 from subprocess import call
34 34
35 35 from setupext import install_data_ext
36 36
37 37 #-------------------------------------------------------------------------------
38 38 # Useful globals and utility functions
39 39 #-------------------------------------------------------------------------------
40 40
41 41 # A few handy globals
42 42 isfile = os.path.isfile
43 43 pjoin = os.path.join
44 44 repo_root = os.path.dirname(os.path.abspath(__file__))
45 45
46 46 def oscmd(s):
47 47 print(">", s)
48 48 os.system(s)
49 49
50 50 # Py3 compatibility hacks, without assuming IPython itself is installed with
51 51 # the full py3compat machinery.
52 52
53 53 try:
54 54 execfile
55 55 except NameError:
56 56 def execfile(fname, globs, locs=None):
57 57 locs = locs or globs
58 58 exec(compile(open(fname).read(), fname, "exec"), globs, locs)
59 59
60 60 # A little utility we'll need below, since glob() does NOT allow you to do
61 61 # exclusion on multiple endings!
62 62 def file_doesnt_endwith(test,endings):
63 63 """Return true if test is a file and its name does NOT end with any
64 64 of the strings listed in endings."""
65 65 if not isfile(test):
66 66 return False
67 67 for e in endings:
68 68 if test.endswith(e):
69 69 return False
70 70 return True
71 71
72 72 #---------------------------------------------------------------------------
73 73 # Basic project information
74 74 #---------------------------------------------------------------------------
75 75
76 76 # release.py contains version, authors, license, url, keywords, etc.
77 77 execfile(pjoin(repo_root, 'IPython','core','release.py'), globals())
78 78
79 79 # Create a dict with the basic information
80 80 # This dict is eventually passed to setup after additional keys are added.
81 81 setup_args = dict(
82 82 name = name,
83 83 version = version,
84 84 description = description,
85 85 long_description = long_description,
86 86 author = author,
87 87 author_email = author_email,
88 88 url = url,
89 89 download_url = download_url,
90 90 license = license,
91 91 platforms = platforms,
92 92 keywords = keywords,
93 93 classifiers = classifiers,
94 94 cmdclass = {'install_data': install_data_ext},
95 95 )
96 96
97 97
98 98 #---------------------------------------------------------------------------
99 99 # Find packages
100 100 #---------------------------------------------------------------------------
101 101
102 102 def find_packages():
103 103 """
104 104 Find all of IPython's packages.
105 105 """
106 106 excludes = ['deathrow', 'quarantine']
107 107 packages = []
108 108 for dir,subdirs,files in os.walk('IPython'):
109 109 package = dir.replace(os.path.sep, '.')
110 110 if any(package.startswith('IPython.'+exc) for exc in excludes):
111 111 # package is to be excluded (e.g. deathrow)
112 112 continue
113 113 if '__init__.py' not in files:
114 114 # not a package
115 115 continue
116 116 packages.append(package)
117 117 return packages
118 118
119 119 #---------------------------------------------------------------------------
120 120 # Find package data
121 121 #---------------------------------------------------------------------------
122 122
123 123 def find_package_data():
124 124 """
125 125 Find IPython's package_data.
126 126 """
127 127 # This is not enough for these things to appear in an sdist.
128 128 # We need to muck with the MANIFEST to get this to work
129 129
130 130 # exclude static things that we don't ship (e.g. mathjax)
131 131 excludes = ['mathjax']
132 132
133 133 # add 'static/' prefix to exclusions, and tuplify for use in startswith
134 134 excludes = tuple([os.path.join('static', ex) for ex in excludes])
135 135
136 136 # walk notebook resources:
137 137 cwd = os.getcwd()
138 138 os.chdir(os.path.join('IPython', 'html'))
139 139 static_walk = list(os.walk('static'))
140 140 static_data = []
141 141 for parent, dirs, files in static_walk:
142 142 if parent.startswith(excludes):
143 143 continue
144 144 for f in files:
145 145 static_data.append(os.path.join(parent, f))
146 146
147 147 os.chdir(os.path.join('tests',))
148 148 js_tests = glob('casperjs/*.*') + glob('casperjs/*/*')
149 149 os.chdir(cwd)
150 150
151 151 package_data = {
152 152 'IPython.config.profile' : ['README*', '*/*.py'],
153 153 'IPython.core.tests' : ['*.png', '*.jpg'],
154 154 'IPython.lib.tests' : ['*.wav'],
155 155 'IPython.testing' : ['*.txt'],
156 156 'IPython.testing.plugin' : ['*.txt'],
157 157 'IPython.html' : ['templates/*'] + static_data,
158 158 'IPython.html.tests' : js_tests,
159 159 'IPython.qt.console' : ['resources/icon/*.svg'],
160 160 'IPython.nbconvert' : ['templates/*.tpl', 'templates/latex/*.tplx',
161 161 'templates/latex/skeleton/*.tplx', 'templates/skeleton/*',
162 162 'templates/reveal_internals/*.tpl', 'tests/files/*.*',
163 163 'exporters/tests/files/*.*'],
164 164 'IPython.nbformat' : ['tests/*.ipynb']
165 165 }
166 166 return package_data
167 167
168 168
169 169 #---------------------------------------------------------------------------
170 170 # Find data files
171 171 #---------------------------------------------------------------------------
172 172
173 173 def make_dir_struct(tag,base,out_base):
174 174 """Make the directory structure of all files below a starting dir.
175 175
176 176 This is just a convenience routine to help build a nested directory
177 177 hierarchy because distutils is too stupid to do this by itself.
178 178
179 179 XXX - this needs a proper docstring!
180 180 """
181 181
182 182 # we'll use these a lot below
183 183 lbase = len(base)
184 184 pathsep = os.path.sep
185 185 lpathsep = len(pathsep)
186 186
187 187 out = []
188 188 for (dirpath,dirnames,filenames) in os.walk(base):
189 189 # we need to strip out the dirpath from the base to map it to the
190 190 # output (installation) path. This requires possibly stripping the
191 191 # path separator, because otherwise pjoin will not work correctly
192 192 # (pjoin('foo/','/bar') returns '/bar').
193 193
194 194 dp_eff = dirpath[lbase:]
195 195 if dp_eff.startswith(pathsep):
196 196 dp_eff = dp_eff[lpathsep:]
197 197 # The output path must be anchored at the out_base marker
198 198 out_path = pjoin(out_base,dp_eff)
199 199 # Now we can generate the final filenames. Since os.walk only produces
200 200 # filenames, we must join back with the dirpath to get full valid file
201 201 # paths:
202 202 pfiles = [pjoin(dirpath,f) for f in filenames]
203 203 # Finally, generate the entry we need, which is a pari of (output
204 204 # path, files) for use as a data_files parameter in install_data.
205 205 out.append((out_path, pfiles))
206 206
207 207 return out
208 208
209 209
210 210 def find_data_files():
211 211 """
212 212 Find IPython's data_files.
213 213
214 214 Most of these are docs.
215 215 """
216 216
217 217 docdirbase = pjoin('share', 'doc', 'ipython')
218 218 manpagebase = pjoin('share', 'man', 'man1')
219 219
220 220 # Simple file lists can be made by hand
221 221 manpages = [f for f in glob(pjoin('docs','man','*.1.gz')) if isfile(f)]
222 222 if not manpages:
223 223 # When running from a source tree, the manpages aren't gzipped
224 224 manpages = [f for f in glob(pjoin('docs','man','*.1')) if isfile(f)]
225 225
226 226 igridhelpfiles = [f for f in glob(pjoin('IPython','extensions','igrid_help.*')) if isfile(f)]
227 227
228 228 # For nested structures, use the utility above
229 229 example_files = make_dir_struct(
230 230 'data',
231 231 pjoin('docs','examples'),
232 232 pjoin(docdirbase,'examples')
233 233 )
234 234 manual_files = make_dir_struct(
235 235 'data',
236 236 pjoin('docs','html'),
237 237 pjoin(docdirbase,'manual')
238 238 )
239 239
240 240 # And assemble the entire output list
241 241 data_files = [ (manpagebase, manpages),
242 242 (pjoin(docdirbase, 'extensions'), igridhelpfiles),
243 243 ] + manual_files + example_files
244 244
245 245 return data_files
246 246
247 247
248 248 def make_man_update_target(manpage):
249 249 """Return a target_update-compliant tuple for the given manpage.
250 250
251 251 Parameters
252 252 ----------
253 253 manpage : string
254 254 Name of the manpage, must include the section number (trailing number).
255 255
256 256 Example
257 257 -------
258 258
259 259 >>> make_man_update_target('ipython.1') #doctest: +NORMALIZE_WHITESPACE
260 260 ('docs/man/ipython.1.gz',
261 261 ['docs/man/ipython.1'],
262 262 'cd docs/man && gzip -9c ipython.1 > ipython.1.gz')
263 263 """
264 264 man_dir = pjoin('docs', 'man')
265 265 manpage_gz = manpage + '.gz'
266 266 manpath = pjoin(man_dir, manpage)
267 267 manpath_gz = pjoin(man_dir, manpage_gz)
268 268 gz_cmd = ( "cd %(man_dir)s && gzip -9c %(manpage)s > %(manpage_gz)s" %
269 269 locals() )
270 270 return (manpath_gz, [manpath], gz_cmd)
271 271
272 272 # The two functions below are copied from IPython.utils.path, so we don't need
273 273 # to import IPython during setup, which fails on Python 3.
274 274
275 275 def target_outdated(target,deps):
276 276 """Determine whether a target is out of date.
277 277
278 278 target_outdated(target,deps) -> 1/0
279 279
280 280 deps: list of filenames which MUST exist.
281 281 target: single filename which may or may not exist.
282 282
283 283 If target doesn't exist or is older than any file listed in deps, return
284 284 true, otherwise return false.
285 285 """
286 286 try:
287 287 target_time = os.path.getmtime(target)
288 288 except os.error:
289 289 return 1
290 290 for dep in deps:
291 291 dep_time = os.path.getmtime(dep)
292 292 if dep_time > target_time:
293 293 #print "For target",target,"Dep failed:",dep # dbg
294 294 #print "times (dep,tar):",dep_time,target_time # dbg
295 295 return 1
296 296 return 0
297 297
298 298
299 299 def target_update(target,deps,cmd):
300 300 """Update a target with a given command given a list of dependencies.
301 301
302 302 target_update(target,deps,cmd) -> runs cmd if target is outdated.
303 303
304 304 This is just a wrapper around target_outdated() which calls the given
305 305 command if target is outdated."""
306 306
307 307 if target_outdated(target,deps):
308 308 os.system(cmd)
309 309
310 310 #---------------------------------------------------------------------------
311 311 # Find scripts
312 312 #---------------------------------------------------------------------------
313 313
314 314 def find_entry_points():
315 315 """Find IPython's scripts.
316 316
317 317 if entry_points is True:
318 318 return setuptools entry_point-style definitions
319 319 else:
320 320 return file paths of plain scripts [default]
321 321
322 322 suffix is appended to script names if entry_points is True, so that the
323 323 Python 3 scripts get named "ipython3" etc.
324 324 """
325 325 ep = [
326 326 'ipython%s = IPython:start_ipython',
327 327 'ipcontroller%s = IPython.parallel.apps.ipcontrollerapp:launch_new_instance',
328 328 'ipengine%s = IPython.parallel.apps.ipengineapp:launch_new_instance',
329 'iplogger%s = IPython.parallel.apps.iploggerapp:launch_new_instance',
330 329 'ipcluster%s = IPython.parallel.apps.ipclusterapp:launch_new_instance',
331 330 'iptest%s = IPython.testing.iptestcontroller:main',
332 'irunner%s = IPython.lib.irunner:main',
333 331 ]
334 332 suffix = str(sys.version_info[0])
335 333 return [e % '' for e in ep] + [e % suffix for e in ep]
336 334
337 335 script_src = """#!{executable}
338 336 # This script was automatically generated by setup.py
339 337 from {mod} import {func}
340 338 {func}()
341 339 """
342 340
343 341 class build_scripts_entrypt(build_scripts):
344 342 def run(self):
345 343 self.mkpath(self.build_dir)
346 344 outfiles = []
347 345 for script in find_entry_points():
348 346 name, entrypt = script.split('=')
349 347 name = name.strip()
350 348 entrypt = entrypt.strip()
351 349 outfile = os.path.join(self.build_dir, name)
352 350 outfiles.append(outfile)
353 351 print('Writing script to', outfile)
354 352
355 353 mod, func = entrypt.split(':')
356 354 with open(outfile, 'w') as f:
357 355 f.write(script_src.format(executable=sys.executable,
358 356 mod=mod, func=func))
359 357
360 358 return outfiles, outfiles
361 359
362 360 class install_lib_symlink(Command):
363 361 user_options = [
364 362 ('install-dir=', 'd', "directory to install to"),
365 363 ]
366 364
367 365 def initialize_options(self):
368 366 self.install_dir = None
369 367
370 368 def finalize_options(self):
371 369 self.set_undefined_options('symlink',
372 370 ('install_lib', 'install_dir'),
373 371 )
374 372
375 373 def run(self):
376 374 if sys.platform == 'win32':
377 375 raise Exception("This doesn't work on Windows.")
378 376 pkg = os.path.join(os.getcwd(), 'IPython')
379 377 dest = os.path.join(self.install_dir, 'IPython')
380 378 print('symlinking %s -> %s' % (pkg, dest))
381 379 try:
382 380 os.symlink(pkg, dest)
383 381 except OSError as e:
384 382 if e.errno == errno.EEXIST:
385 383 print('ALREADY EXISTS')
386 384 else:
387 385 raise
388 386
389 387 class install_symlinked(install):
390 388 def run(self):
391 389 if sys.platform == 'win32':
392 390 raise Exception("This doesn't work on Windows.")
393 391 install.run(self)
394 392
395 393 # 'sub_commands': a list of commands this command might have to run to
396 394 # get its work done. See cmd.py for more info.
397 395 sub_commands = [('install_lib_symlink', lambda self:True),
398 396 ('install_scripts_sym', lambda self:True),
399 397 ]
400 398
401 399 class install_scripts_for_symlink(install_scripts):
402 400 """Redefined to get options from 'symlink' instead of 'install'.
403 401
404 402 I love distutils almost as much as I love setuptools.
405 403 """
406 404 def finalize_options(self):
407 405 self.set_undefined_options('build', ('build_scripts', 'build_dir'))
408 406 self.set_undefined_options('symlink',
409 407 ('install_scripts', 'install_dir'),
410 408 ('force', 'force'),
411 409 ('skip_build', 'skip_build'),
412 410 )
413 411
414 412 #---------------------------------------------------------------------------
415 413 # Verify all dependencies
416 414 #---------------------------------------------------------------------------
417 415
418 416 def check_for_dependencies():
419 417 """Check for IPython's dependencies.
420 418
421 419 This function should NOT be called if running under setuptools!
422 420 """
423 421 from setupext.setupext import (
424 422 print_line, print_raw, print_status,
425 423 check_for_sphinx, check_for_pygments,
426 424 check_for_nose, check_for_pexpect,
427 425 check_for_pyzmq, check_for_readline,
428 426 check_for_jinja2, check_for_tornado
429 427 )
430 428 print_line()
431 429 print_raw("BUILDING IPYTHON")
432 430 print_status('python', sys.version)
433 431 print_status('platform', sys.platform)
434 432 if sys.platform == 'win32':
435 433 print_status('Windows version', sys.getwindowsversion())
436 434
437 435 print_raw("")
438 436 print_raw("OPTIONAL DEPENDENCIES")
439 437
440 438 check_for_sphinx()
441 439 check_for_pygments()
442 440 check_for_nose()
443 441 check_for_pexpect()
444 442 check_for_pyzmq()
445 443 check_for_tornado()
446 444 check_for_readline()
447 445 check_for_jinja2()
448 446
449 447 #---------------------------------------------------------------------------
450 448 # VCS related
451 449 #---------------------------------------------------------------------------
452 450
453 451 # utils.submodule has checks for submodule status
454 452 execfile(pjoin('IPython','utils','submodule.py'), globals())
455 453
456 454 class UpdateSubmodules(Command):
457 455 """Update git submodules
458 456
459 457 IPython's external javascript dependencies live in a separate repo.
460 458 """
461 459 description = "Update git submodules"
462 460 user_options = []
463 461
464 462 def initialize_options(self):
465 463 pass
466 464
467 465 def finalize_options(self):
468 466 pass
469 467
470 468 def run(self):
471 469 failure = False
472 470 try:
473 471 self.spawn('git submodule init'.split())
474 472 self.spawn('git submodule update --recursive'.split())
475 473 except Exception as e:
476 474 failure = e
477 475 print(e)
478 476
479 477 if not check_submodule_status(repo_root) == 'clean':
480 478 print("submodules could not be checked out")
481 479 sys.exit(1)
482 480
483 481
484 482 def git_prebuild(pkg_dir, build_cmd=build_py):
485 483 """Return extended build or sdist command class for recording commit
486 484
487 485 records git commit in IPython.utils._sysinfo.commit
488 486
489 487 for use in IPython.utils.sysinfo.sys_info() calls after installation.
490 488
491 489 Also ensures that submodules exist prior to running
492 490 """
493 491
494 492 class MyBuildPy(build_cmd):
495 493 ''' Subclass to write commit data into installation tree '''
496 494 def run(self):
497 495 build_cmd.run(self)
498 496 # this one will only fire for build commands
499 497 if hasattr(self, 'build_lib'):
500 498 self._record_commit(self.build_lib)
501 499
502 500 def make_release_tree(self, base_dir, files):
503 501 # this one will fire for sdist
504 502 build_cmd.make_release_tree(self, base_dir, files)
505 503 self._record_commit(base_dir)
506 504
507 505 def _record_commit(self, base_dir):
508 506 import subprocess
509 507 proc = subprocess.Popen('git rev-parse --short HEAD',
510 508 stdout=subprocess.PIPE,
511 509 stderr=subprocess.PIPE,
512 510 shell=True)
513 511 repo_commit, _ = proc.communicate()
514 512 repo_commit = repo_commit.strip().decode("ascii")
515 513
516 514 out_pth = pjoin(base_dir, pkg_dir, 'utils', '_sysinfo.py')
517 515 if os.path.isfile(out_pth) and not repo_commit:
518 516 # nothing to write, don't clobber
519 517 return
520 518
521 519 print("writing git commit '%s' to %s" % (repo_commit, out_pth))
522 520
523 521 # remove to avoid overwriting original via hard link
524 522 try:
525 523 os.remove(out_pth)
526 524 except (IOError, OSError):
527 525 pass
528 526 with open(out_pth, 'w') as out_file:
529 527 out_file.writelines([
530 528 '# GENERATED BY setup.py\n',
531 529 'commit = "%s"\n' % repo_commit,
532 530 ])
533 531 return require_submodules(MyBuildPy)
534 532
535 533
536 534 def require_submodules(command):
537 535 """decorator for instructing a command to check for submodules before running"""
538 536 class DecoratedCommand(command):
539 537 def run(self):
540 538 if not check_submodule_status(repo_root) == 'clean':
541 539 print("submodules missing! Run `setup.py submodule` and try again")
542 540 sys.exit(1)
543 541 command.run(self)
544 542 return DecoratedCommand
545 543
546 544 #---------------------------------------------------------------------------
547 545 # Notebook related
548 546 #---------------------------------------------------------------------------
549 547
550 548 class CompileCSS(Command):
551 549 """Recompile Notebook CSS
552 550
553 551 Regenerate the compiled CSS from LESS sources.
554 552
555 553 Requires various dev dependencies, such as fabric and lessc.
556 554 """
557 555 description = "Recompile Notebook CSS"
558 556 user_options = []
559 557
560 558 def initialize_options(self):
561 559 pass
562 560
563 561 def finalize_options(self):
564 562 pass
565 563
566 564 def run(self):
567 565 call("fab css", shell=True, cwd=pjoin(repo_root, "IPython", "html"))
568 566
569 567 class JavascriptVersion(Command):
570 568 """write the javascript version to notebook javascript"""
571 569 description = "Write IPython version to javascript"
572 570 user_options = []
573 571
574 572 def initialize_options(self):
575 573 pass
576 574
577 575 def finalize_options(self):
578 576 pass
579 577
580 578 def run(self):
581 579 nsfile = pjoin(repo_root, "IPython", "html", "static", "base", "js", "namespace.js")
582 580 with open(nsfile) as f:
583 581 lines = f.readlines()
584 582 with open(nsfile, 'w') as f:
585 583 for line in lines:
586 584 if line.startswith("IPython.version"):
587 585 line = 'IPython.version = "{0}";\n'.format(version)
588 586 f.write(line)
589 587
1 NO CONTENT: file was removed
1 NO CONTENT: file was removed
1 NO CONTENT: file was removed
1 NO CONTENT: file was removed
General Comments 0
You need to be logged in to leave comments. Login now