##// END OF EJS Templates
Walk nbconvert templates directory for package data files
Thomas Kluyver -
Show More
@@ -1,597 +1,600 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
150 os.chdir(os.path.join(cwd, 'IPython', 'nbconvert'))
151 nbconvert_templates = [os.path.join(dirpath, '*')
152 for dirpath, _, _ in os.walk('templates')]
153
149 154 os.chdir(cwd)
150 155
151 156 package_data = {
152 157 'IPython.config.profile' : ['README*', '*/*.py'],
153 158 'IPython.core.tests' : ['*.png', '*.jpg'],
154 159 'IPython.lib.tests' : ['*.wav'],
155 160 'IPython.testing' : ['*.txt'],
156 161 'IPython.testing.plugin' : ['*.txt'],
157 162 'IPython.html' : ['templates/*'] + static_data,
158 163 'IPython.html.tests' : js_tests,
159 164 'IPython.qt.console' : ['resources/icon/*.svg'],
160 'IPython.nbconvert' : ['templates/*.tpl', 'templates/latex/*.tplx',
161 'templates/latex/skeleton/*.tplx', 'templates/skeleton/*',
162 'templates/reveal_internals/*.tpl', 'tests/files/*.*',
163 'exporters/tests/files/*.*'],
165 'IPython.nbconvert' : nbconvert_templates +
166 ['tests/files/*.*', 'exporters/tests/files/*.*'],
164 167 'IPython.nbformat' : ['tests/*.ipynb']
165 168 }
166 169 return package_data
167 170
168 171
169 172 #---------------------------------------------------------------------------
170 173 # Find data files
171 174 #---------------------------------------------------------------------------
172 175
173 176 def make_dir_struct(tag,base,out_base):
174 177 """Make the directory structure of all files below a starting dir.
175 178
176 179 This is just a convenience routine to help build a nested directory
177 180 hierarchy because distutils is too stupid to do this by itself.
178 181
179 182 XXX - this needs a proper docstring!
180 183 """
181 184
182 185 # we'll use these a lot below
183 186 lbase = len(base)
184 187 pathsep = os.path.sep
185 188 lpathsep = len(pathsep)
186 189
187 190 out = []
188 191 for (dirpath,dirnames,filenames) in os.walk(base):
189 192 # we need to strip out the dirpath from the base to map it to the
190 193 # output (installation) path. This requires possibly stripping the
191 194 # path separator, because otherwise pjoin will not work correctly
192 195 # (pjoin('foo/','/bar') returns '/bar').
193 196
194 197 dp_eff = dirpath[lbase:]
195 198 if dp_eff.startswith(pathsep):
196 199 dp_eff = dp_eff[lpathsep:]
197 200 # The output path must be anchored at the out_base marker
198 201 out_path = pjoin(out_base,dp_eff)
199 202 # Now we can generate the final filenames. Since os.walk only produces
200 203 # filenames, we must join back with the dirpath to get full valid file
201 204 # paths:
202 205 pfiles = [pjoin(dirpath,f) for f in filenames]
203 206 # Finally, generate the entry we need, which is a pari of (output
204 207 # path, files) for use as a data_files parameter in install_data.
205 208 out.append((out_path, pfiles))
206 209
207 210 return out
208 211
209 212
210 213 def find_data_files():
211 214 """
212 215 Find IPython's data_files.
213 216
214 217 Most of these are docs.
215 218 """
216 219
217 220 docdirbase = pjoin('share', 'doc', 'ipython')
218 221 manpagebase = pjoin('share', 'man', 'man1')
219 222
220 223 # Simple file lists can be made by hand
221 224 manpages = [f for f in glob(pjoin('docs','man','*.1.gz')) if isfile(f)]
222 225 if not manpages:
223 226 # When running from a source tree, the manpages aren't gzipped
224 227 manpages = [f for f in glob(pjoin('docs','man','*.1')) if isfile(f)]
225 228
226 229 igridhelpfiles = [f for f in glob(pjoin('IPython','extensions','igrid_help.*')) if isfile(f)]
227 230
228 231 # For nested structures, use the utility above
229 232 example_files = make_dir_struct(
230 233 'data',
231 234 pjoin('docs','examples'),
232 235 pjoin(docdirbase,'examples')
233 236 )
234 237 manual_files = make_dir_struct(
235 238 'data',
236 239 pjoin('docs','html'),
237 240 pjoin(docdirbase,'manual')
238 241 )
239 242
240 243 # And assemble the entire output list
241 244 data_files = [ (manpagebase, manpages),
242 245 (pjoin(docdirbase, 'extensions'), igridhelpfiles),
243 246 ] + manual_files + example_files
244 247
245 248 return data_files
246 249
247 250
248 251 def make_man_update_target(manpage):
249 252 """Return a target_update-compliant tuple for the given manpage.
250 253
251 254 Parameters
252 255 ----------
253 256 manpage : string
254 257 Name of the manpage, must include the section number (trailing number).
255 258
256 259 Example
257 260 -------
258 261
259 262 >>> make_man_update_target('ipython.1') #doctest: +NORMALIZE_WHITESPACE
260 263 ('docs/man/ipython.1.gz',
261 264 ['docs/man/ipython.1'],
262 265 'cd docs/man && gzip -9c ipython.1 > ipython.1.gz')
263 266 """
264 267 man_dir = pjoin('docs', 'man')
265 268 manpage_gz = manpage + '.gz'
266 269 manpath = pjoin(man_dir, manpage)
267 270 manpath_gz = pjoin(man_dir, manpage_gz)
268 271 gz_cmd = ( "cd %(man_dir)s && gzip -9c %(manpage)s > %(manpage_gz)s" %
269 272 locals() )
270 273 return (manpath_gz, [manpath], gz_cmd)
271 274
272 275 # The two functions below are copied from IPython.utils.path, so we don't need
273 276 # to import IPython during setup, which fails on Python 3.
274 277
275 278 def target_outdated(target,deps):
276 279 """Determine whether a target is out of date.
277 280
278 281 target_outdated(target,deps) -> 1/0
279 282
280 283 deps: list of filenames which MUST exist.
281 284 target: single filename which may or may not exist.
282 285
283 286 If target doesn't exist or is older than any file listed in deps, return
284 287 true, otherwise return false.
285 288 """
286 289 try:
287 290 target_time = os.path.getmtime(target)
288 291 except os.error:
289 292 return 1
290 293 for dep in deps:
291 294 dep_time = os.path.getmtime(dep)
292 295 if dep_time > target_time:
293 296 #print "For target",target,"Dep failed:",dep # dbg
294 297 #print "times (dep,tar):",dep_time,target_time # dbg
295 298 return 1
296 299 return 0
297 300
298 301
299 302 def target_update(target,deps,cmd):
300 303 """Update a target with a given command given a list of dependencies.
301 304
302 305 target_update(target,deps,cmd) -> runs cmd if target is outdated.
303 306
304 307 This is just a wrapper around target_outdated() which calls the given
305 308 command if target is outdated."""
306 309
307 310 if target_outdated(target,deps):
308 311 os.system(cmd)
309 312
310 313 #---------------------------------------------------------------------------
311 314 # Find scripts
312 315 #---------------------------------------------------------------------------
313 316
314 317 def find_entry_points():
315 318 """Find IPython's scripts.
316 319
317 320 if entry_points is True:
318 321 return setuptools entry_point-style definitions
319 322 else:
320 323 return file paths of plain scripts [default]
321 324
322 325 suffix is appended to script names if entry_points is True, so that the
323 326 Python 3 scripts get named "ipython3" etc.
324 327 """
325 328 ep = [
326 329 'ipython%s = IPython:start_ipython',
327 330 'ipcontroller%s = IPython.parallel.apps.ipcontrollerapp:launch_new_instance',
328 331 'ipengine%s = IPython.parallel.apps.ipengineapp:launch_new_instance',
329 332 'ipcluster%s = IPython.parallel.apps.ipclusterapp:launch_new_instance',
330 333 'iptest%s = IPython.testing.iptestcontroller:main',
331 334 ]
332 335 suffix = str(sys.version_info[0])
333 336 return [e % '' for e in ep] + [e % suffix for e in ep]
334 337
335 338 script_src = """#!{executable}
336 339 # This script was automatically generated by setup.py
337 340 if __name__ == '__main__':
338 341 from {mod} import {func}
339 342 {func}()
340 343 """
341 344
342 345 class build_scripts_entrypt(build_scripts):
343 346 def run(self):
344 347 self.mkpath(self.build_dir)
345 348 outfiles = []
346 349 for script in find_entry_points():
347 350 name, entrypt = script.split('=')
348 351 name = name.strip()
349 352 entrypt = entrypt.strip()
350 353 outfile = os.path.join(self.build_dir, name)
351 354 outfiles.append(outfile)
352 355 print('Writing script to', outfile)
353 356
354 357 mod, func = entrypt.split(':')
355 358 with open(outfile, 'w') as f:
356 359 f.write(script_src.format(executable=sys.executable,
357 360 mod=mod, func=func))
358 361
359 362 return outfiles, outfiles
360 363
361 364 class install_lib_symlink(Command):
362 365 user_options = [
363 366 ('install-dir=', 'd', "directory to install to"),
364 367 ]
365 368
366 369 def initialize_options(self):
367 370 self.install_dir = None
368 371
369 372 def finalize_options(self):
370 373 self.set_undefined_options('symlink',
371 374 ('install_lib', 'install_dir'),
372 375 )
373 376
374 377 def run(self):
375 378 if sys.platform == 'win32':
376 379 raise Exception("This doesn't work on Windows.")
377 380 pkg = os.path.join(os.getcwd(), 'IPython')
378 381 dest = os.path.join(self.install_dir, 'IPython')
379 382 if os.path.islink(dest):
380 383 print('removing existing symlink at %s' % dest)
381 384 os.unlink(dest)
382 385 print('symlinking %s -> %s' % (pkg, dest))
383 386 os.symlink(pkg, dest)
384 387
385 388 class unsymlink(install):
386 389 def run(self):
387 390 dest = os.path.join(self.install_lib, 'IPython')
388 391 if os.path.islink(dest):
389 392 print('removing symlink at %s' % dest)
390 393 os.unlink(dest)
391 394 else:
392 395 print('No symlink exists at %s' % dest)
393 396
394 397 class install_symlinked(install):
395 398 def run(self):
396 399 if sys.platform == 'win32':
397 400 raise Exception("This doesn't work on Windows.")
398 401
399 402 # Run all sub-commands (at least those that need to be run)
400 403 for cmd_name in self.get_sub_commands():
401 404 self.run_command(cmd_name)
402 405
403 406 # 'sub_commands': a list of commands this command might have to run to
404 407 # get its work done. See cmd.py for more info.
405 408 sub_commands = [('install_lib_symlink', lambda self:True),
406 409 ('install_scripts_sym', lambda self:True),
407 410 ]
408 411
409 412 class install_scripts_for_symlink(install_scripts):
410 413 """Redefined to get options from 'symlink' instead of 'install'.
411 414
412 415 I love distutils almost as much as I love setuptools.
413 416 """
414 417 def finalize_options(self):
415 418 self.set_undefined_options('build', ('build_scripts', 'build_dir'))
416 419 self.set_undefined_options('symlink',
417 420 ('install_scripts', 'install_dir'),
418 421 ('force', 'force'),
419 422 ('skip_build', 'skip_build'),
420 423 )
421 424
422 425 #---------------------------------------------------------------------------
423 426 # Verify all dependencies
424 427 #---------------------------------------------------------------------------
425 428
426 429 def check_for_dependencies():
427 430 """Check for IPython's dependencies.
428 431
429 432 This function should NOT be called if running under setuptools!
430 433 """
431 434 from setupext.setupext import (
432 435 print_line, print_raw, print_status,
433 436 check_for_sphinx, check_for_pygments,
434 437 check_for_nose, check_for_pexpect,
435 438 check_for_pyzmq, check_for_readline,
436 439 check_for_jinja2, check_for_tornado
437 440 )
438 441 print_line()
439 442 print_raw("BUILDING IPYTHON")
440 443 print_status('python', sys.version)
441 444 print_status('platform', sys.platform)
442 445 if sys.platform == 'win32':
443 446 print_status('Windows version', sys.getwindowsversion())
444 447
445 448 print_raw("")
446 449 print_raw("OPTIONAL DEPENDENCIES")
447 450
448 451 check_for_sphinx()
449 452 check_for_pygments()
450 453 check_for_nose()
451 454 check_for_pexpect()
452 455 check_for_pyzmq()
453 456 check_for_tornado()
454 457 check_for_readline()
455 458 check_for_jinja2()
456 459
457 460 #---------------------------------------------------------------------------
458 461 # VCS related
459 462 #---------------------------------------------------------------------------
460 463
461 464 # utils.submodule has checks for submodule status
462 465 execfile(pjoin('IPython','utils','submodule.py'), globals())
463 466
464 467 class UpdateSubmodules(Command):
465 468 """Update git submodules
466 469
467 470 IPython's external javascript dependencies live in a separate repo.
468 471 """
469 472 description = "Update git submodules"
470 473 user_options = []
471 474
472 475 def initialize_options(self):
473 476 pass
474 477
475 478 def finalize_options(self):
476 479 pass
477 480
478 481 def run(self):
479 482 failure = False
480 483 try:
481 484 self.spawn('git submodule init'.split())
482 485 self.spawn('git submodule update --recursive'.split())
483 486 except Exception as e:
484 487 failure = e
485 488 print(e)
486 489
487 490 if not check_submodule_status(repo_root) == 'clean':
488 491 print("submodules could not be checked out")
489 492 sys.exit(1)
490 493
491 494
492 495 def git_prebuild(pkg_dir, build_cmd=build_py):
493 496 """Return extended build or sdist command class for recording commit
494 497
495 498 records git commit in IPython.utils._sysinfo.commit
496 499
497 500 for use in IPython.utils.sysinfo.sys_info() calls after installation.
498 501
499 502 Also ensures that submodules exist prior to running
500 503 """
501 504
502 505 class MyBuildPy(build_cmd):
503 506 ''' Subclass to write commit data into installation tree '''
504 507 def run(self):
505 508 build_cmd.run(self)
506 509 # this one will only fire for build commands
507 510 if hasattr(self, 'build_lib'):
508 511 self._record_commit(self.build_lib)
509 512
510 513 def make_release_tree(self, base_dir, files):
511 514 # this one will fire for sdist
512 515 build_cmd.make_release_tree(self, base_dir, files)
513 516 self._record_commit(base_dir)
514 517
515 518 def _record_commit(self, base_dir):
516 519 import subprocess
517 520 proc = subprocess.Popen('git rev-parse --short HEAD',
518 521 stdout=subprocess.PIPE,
519 522 stderr=subprocess.PIPE,
520 523 shell=True)
521 524 repo_commit, _ = proc.communicate()
522 525 repo_commit = repo_commit.strip().decode("ascii")
523 526
524 527 out_pth = pjoin(base_dir, pkg_dir, 'utils', '_sysinfo.py')
525 528 if os.path.isfile(out_pth) and not repo_commit:
526 529 # nothing to write, don't clobber
527 530 return
528 531
529 532 print("writing git commit '%s' to %s" % (repo_commit, out_pth))
530 533
531 534 # remove to avoid overwriting original via hard link
532 535 try:
533 536 os.remove(out_pth)
534 537 except (IOError, OSError):
535 538 pass
536 539 with open(out_pth, 'w') as out_file:
537 540 out_file.writelines([
538 541 '# GENERATED BY setup.py\n',
539 542 'commit = "%s"\n' % repo_commit,
540 543 ])
541 544 return require_submodules(MyBuildPy)
542 545
543 546
544 547 def require_submodules(command):
545 548 """decorator for instructing a command to check for submodules before running"""
546 549 class DecoratedCommand(command):
547 550 def run(self):
548 551 if not check_submodule_status(repo_root) == 'clean':
549 552 print("submodules missing! Run `setup.py submodule` and try again")
550 553 sys.exit(1)
551 554 command.run(self)
552 555 return DecoratedCommand
553 556
554 557 #---------------------------------------------------------------------------
555 558 # Notebook related
556 559 #---------------------------------------------------------------------------
557 560
558 561 class CompileCSS(Command):
559 562 """Recompile Notebook CSS
560 563
561 564 Regenerate the compiled CSS from LESS sources.
562 565
563 566 Requires various dev dependencies, such as fabric and lessc.
564 567 """
565 568 description = "Recompile Notebook CSS"
566 569 user_options = []
567 570
568 571 def initialize_options(self):
569 572 pass
570 573
571 574 def finalize_options(self):
572 575 pass
573 576
574 577 def run(self):
575 578 call("fab css", shell=True, cwd=pjoin(repo_root, "IPython", "html"))
576 579
577 580 class JavascriptVersion(Command):
578 581 """write the javascript version to notebook javascript"""
579 582 description = "Write IPython version to javascript"
580 583 user_options = []
581 584
582 585 def initialize_options(self):
583 586 pass
584 587
585 588 def finalize_options(self):
586 589 pass
587 590
588 591 def run(self):
589 592 nsfile = pjoin(repo_root, "IPython", "html", "static", "base", "js", "namespace.js")
590 593 with open(nsfile) as f:
591 594 lines = f.readlines()
592 595 with open(nsfile, 'w') as f:
593 596 for line in lines:
594 597 if line.startswith("IPython.version"):
595 598 line = 'IPython.version = "{0}";\n'.format(version)
596 599 f.write(line)
597 600
General Comments 0
You need to be logged in to leave comments. Login now