##// END OF EJS Templates
If symlink already exists, clobber it with current one
Thomas Kluyver -
Show More
@@ -1,593 +1,589 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 329 'iplogger%s = IPython.parallel.apps.iploggerapp:launch_new_instance',
330 330 'ipcluster%s = IPython.parallel.apps.ipclusterapp:launch_new_instance',
331 331 'iptest%s = IPython.testing.iptestcontroller:main',
332 332 'irunner%s = IPython.lib.irunner:main',
333 333 ]
334 334 suffix = str(sys.version_info[0])
335 335 return [e % '' for e in ep] + [e % suffix for e in ep]
336 336
337 337 script_src = """#!{executable}
338 338 # This script was automatically generated by setup.py
339 339 from {mod} import {func}
340 340 {func}()
341 341 """
342 342
343 343 class build_scripts_entrypt(build_scripts):
344 344 def run(self):
345 345 self.mkpath(self.build_dir)
346 346 outfiles = []
347 347 for script in find_entry_points():
348 348 name, entrypt = script.split('=')
349 349 name = name.strip()
350 350 entrypt = entrypt.strip()
351 351 outfile = os.path.join(self.build_dir, name)
352 352 outfiles.append(outfile)
353 353 print('Writing script to', outfile)
354 354
355 355 mod, func = entrypt.split(':')
356 356 with open(outfile, 'w') as f:
357 357 f.write(script_src.format(executable=sys.executable,
358 358 mod=mod, func=func))
359 359
360 360 return outfiles, outfiles
361 361
362 362 class install_lib_symlink(Command):
363 363 user_options = [
364 364 ('install-dir=', 'd', "directory to install to"),
365 365 ]
366 366
367 367 def initialize_options(self):
368 368 self.install_dir = None
369 369
370 370 def finalize_options(self):
371 371 self.set_undefined_options('symlink',
372 372 ('install_lib', 'install_dir'),
373 373 )
374 374
375 375 def run(self):
376 376 if sys.platform == 'win32':
377 377 raise Exception("This doesn't work on Windows.")
378 378 pkg = os.path.join(os.getcwd(), 'IPython')
379 379 dest = os.path.join(self.install_dir, 'IPython')
380 if os.path.islink(dest):
381 print('removing existing symlink at %s' % dest)
382 os.unlink(dest)
380 383 print('symlinking %s -> %s' % (pkg, dest))
381 try:
382 os.symlink(pkg, dest)
383 except OSError as e:
384 if e.errno == errno.EEXIST and os.path.islink(dest) \
385 and os.path.realpath(dest) == pkg:
386 print('Symlink already exists')
387 else:
388 raise
384 os.symlink(pkg, dest)
389 385
390 386 class install_symlinked(install):
391 387 def run(self):
392 388 if sys.platform == 'win32':
393 389 raise Exception("This doesn't work on Windows.")
394 390
395 391 # Run all sub-commands (at least those that need to be run)
396 392 for cmd_name in self.get_sub_commands():
397 393 self.run_command(cmd_name)
398 394
399 395 # 'sub_commands': a list of commands this command might have to run to
400 396 # get its work done. See cmd.py for more info.
401 397 sub_commands = [('install_lib_symlink', lambda self:True),
402 398 ('install_scripts_sym', lambda self:True),
403 399 ]
404 400
405 401 class install_scripts_for_symlink(install_scripts):
406 402 """Redefined to get options from 'symlink' instead of 'install'.
407 403
408 404 I love distutils almost as much as I love setuptools.
409 405 """
410 406 def finalize_options(self):
411 407 self.set_undefined_options('build', ('build_scripts', 'build_dir'))
412 408 self.set_undefined_options('symlink',
413 409 ('install_scripts', 'install_dir'),
414 410 ('force', 'force'),
415 411 ('skip_build', 'skip_build'),
416 412 )
417 413
418 414 #---------------------------------------------------------------------------
419 415 # Verify all dependencies
420 416 #---------------------------------------------------------------------------
421 417
422 418 def check_for_dependencies():
423 419 """Check for IPython's dependencies.
424 420
425 421 This function should NOT be called if running under setuptools!
426 422 """
427 423 from setupext.setupext import (
428 424 print_line, print_raw, print_status,
429 425 check_for_sphinx, check_for_pygments,
430 426 check_for_nose, check_for_pexpect,
431 427 check_for_pyzmq, check_for_readline,
432 428 check_for_jinja2, check_for_tornado
433 429 )
434 430 print_line()
435 431 print_raw("BUILDING IPYTHON")
436 432 print_status('python', sys.version)
437 433 print_status('platform', sys.platform)
438 434 if sys.platform == 'win32':
439 435 print_status('Windows version', sys.getwindowsversion())
440 436
441 437 print_raw("")
442 438 print_raw("OPTIONAL DEPENDENCIES")
443 439
444 440 check_for_sphinx()
445 441 check_for_pygments()
446 442 check_for_nose()
447 443 check_for_pexpect()
448 444 check_for_pyzmq()
449 445 check_for_tornado()
450 446 check_for_readline()
451 447 check_for_jinja2()
452 448
453 449 #---------------------------------------------------------------------------
454 450 # VCS related
455 451 #---------------------------------------------------------------------------
456 452
457 453 # utils.submodule has checks for submodule status
458 454 execfile(pjoin('IPython','utils','submodule.py'), globals())
459 455
460 456 class UpdateSubmodules(Command):
461 457 """Update git submodules
462 458
463 459 IPython's external javascript dependencies live in a separate repo.
464 460 """
465 461 description = "Update git submodules"
466 462 user_options = []
467 463
468 464 def initialize_options(self):
469 465 pass
470 466
471 467 def finalize_options(self):
472 468 pass
473 469
474 470 def run(self):
475 471 failure = False
476 472 try:
477 473 self.spawn('git submodule init'.split())
478 474 self.spawn('git submodule update --recursive'.split())
479 475 except Exception as e:
480 476 failure = e
481 477 print(e)
482 478
483 479 if not check_submodule_status(repo_root) == 'clean':
484 480 print("submodules could not be checked out")
485 481 sys.exit(1)
486 482
487 483
488 484 def git_prebuild(pkg_dir, build_cmd=build_py):
489 485 """Return extended build or sdist command class for recording commit
490 486
491 487 records git commit in IPython.utils._sysinfo.commit
492 488
493 489 for use in IPython.utils.sysinfo.sys_info() calls after installation.
494 490
495 491 Also ensures that submodules exist prior to running
496 492 """
497 493
498 494 class MyBuildPy(build_cmd):
499 495 ''' Subclass to write commit data into installation tree '''
500 496 def run(self):
501 497 build_cmd.run(self)
502 498 # this one will only fire for build commands
503 499 if hasattr(self, 'build_lib'):
504 500 self._record_commit(self.build_lib)
505 501
506 502 def make_release_tree(self, base_dir, files):
507 503 # this one will fire for sdist
508 504 build_cmd.make_release_tree(self, base_dir, files)
509 505 self._record_commit(base_dir)
510 506
511 507 def _record_commit(self, base_dir):
512 508 import subprocess
513 509 proc = subprocess.Popen('git rev-parse --short HEAD',
514 510 stdout=subprocess.PIPE,
515 511 stderr=subprocess.PIPE,
516 512 shell=True)
517 513 repo_commit, _ = proc.communicate()
518 514 repo_commit = repo_commit.strip().decode("ascii")
519 515
520 516 out_pth = pjoin(base_dir, pkg_dir, 'utils', '_sysinfo.py')
521 517 if os.path.isfile(out_pth) and not repo_commit:
522 518 # nothing to write, don't clobber
523 519 return
524 520
525 521 print("writing git commit '%s' to %s" % (repo_commit, out_pth))
526 522
527 523 # remove to avoid overwriting original via hard link
528 524 try:
529 525 os.remove(out_pth)
530 526 except (IOError, OSError):
531 527 pass
532 528 with open(out_pth, 'w') as out_file:
533 529 out_file.writelines([
534 530 '# GENERATED BY setup.py\n',
535 531 'commit = "%s"\n' % repo_commit,
536 532 ])
537 533 return require_submodules(MyBuildPy)
538 534
539 535
540 536 def require_submodules(command):
541 537 """decorator for instructing a command to check for submodules before running"""
542 538 class DecoratedCommand(command):
543 539 def run(self):
544 540 if not check_submodule_status(repo_root) == 'clean':
545 541 print("submodules missing! Run `setup.py submodule` and try again")
546 542 sys.exit(1)
547 543 command.run(self)
548 544 return DecoratedCommand
549 545
550 546 #---------------------------------------------------------------------------
551 547 # Notebook related
552 548 #---------------------------------------------------------------------------
553 549
554 550 class CompileCSS(Command):
555 551 """Recompile Notebook CSS
556 552
557 553 Regenerate the compiled CSS from LESS sources.
558 554
559 555 Requires various dev dependencies, such as fabric and lessc.
560 556 """
561 557 description = "Recompile Notebook CSS"
562 558 user_options = []
563 559
564 560 def initialize_options(self):
565 561 pass
566 562
567 563 def finalize_options(self):
568 564 pass
569 565
570 566 def run(self):
571 567 call("fab css", shell=True, cwd=pjoin(repo_root, "IPython", "html"))
572 568
573 569 class JavascriptVersion(Command):
574 570 """write the javascript version to notebook javascript"""
575 571 description = "Write IPython version to javascript"
576 572 user_options = []
577 573
578 574 def initialize_options(self):
579 575 pass
580 576
581 577 def finalize_options(self):
582 578 pass
583 579
584 580 def run(self):
585 581 nsfile = pjoin(repo_root, "IPython", "html", "static", "base", "js", "namespace.js")
586 582 with open(nsfile) as f:
587 583 lines = f.readlines()
588 584 with open(nsfile, 'w') as f:
589 585 for line in lines:
590 586 if line.startswith("IPython.version"):
591 587 line = 'IPython.version = "{0}";\n'.format(version)
592 588 f.write(line)
593 589
General Comments 0
You need to be logged in to leave comments. Login now