##// END OF EJS Templates
Merge pull request #3275 from minrk/submodule-hooks...
Brian E. Granger -
r10601:5acb5e37 merge
parent child Browse files
Show More
@@ -0,0 +1,92 b''
1 """utilities for checking submodule status"""
2
3 #-----------------------------------------------------------------------------
4 # Copyright (C) 2013 The IPython Development Team
5 #
6 # Distributed under the terms of the BSD License. The full license is in
7 # the file COPYING, distributed as part of this software.
8 #-----------------------------------------------------------------------------
9
10 #-----------------------------------------------------------------------------
11 # Imports
12 #-----------------------------------------------------------------------------
13
14 import os
15 import subprocess
16 import sys
17
18 #-----------------------------------------------------------------------------
19 # Globals
20 #-----------------------------------------------------------------------------
21
22 pjoin = os.path.join
23
24 #-----------------------------------------------------------------------------
25 # Code
26 #-----------------------------------------------------------------------------
27
28 def ipython_parent():
29 """return IPython's parent (i.e. root if run from git)"""
30 from IPython.utils.path import get_ipython_package_dir
31 return os.path.abspath(os.path.dirname(get_ipython_package_dir()))
32
33 def ipython_submodules(root):
34 """return IPython submodules relative to root"""
35 return [
36 pjoin(root, 'IPython', 'frontend', 'html', 'notebook', 'static', 'components'),
37 ]
38
39 def is_repo(d):
40 """is d a git repo?"""
41 return os.path.exists(pjoin(d, '.git'))
42
43 def check_submodule_status(root=None):
44 """check submodule status
45
46 Has three return values:
47
48 'missing' - submodules are absent
49 'unclean' - submodules have unstaged changes
50 'clean' - all submodules are up to date
51 """
52
53 if hasattr(sys, "frozen"):
54 # frozen via py2exe or similar, don't bother
55 return 'clean'
56
57 if not root:
58 root = ipython_parent()
59
60 submodules = ipython_submodules(root)
61
62 for submodule in submodules:
63 if not os.path.exists(submodule):
64 return 'missing'
65
66 if not is_repo(root):
67 # not in git, assume clean
68 return 'clean'
69
70 # check with git submodule status
71 proc = subprocess.Popen('git submodule status',
72 stdout=subprocess.PIPE,
73 stderr=subprocess.PIPE,
74 shell=True,
75 cwd=root,
76 )
77 status, _ = proc.communicate()
78 status = status.decode("ascii")
79
80 for line in status.splitlines():
81 if status.startswith('-'):
82 return 'missing'
83 elif status.startswith('+'):
84 return 'unclean'
85
86 return 'clean'
87
88 def update_submodules(repo_dir):
89 """update submodules in a repo"""
90 subprocess.check_call("git submodule init", cwd=repo_dir, shell=True)
91 subprocess.check_call("git submodule update --recursive", cwd=repo_dir, shell=True)
92
@@ -0,0 +1,13 b''
1 git hooks for IPython
2
3 add these to your `.git/hooks`
4
5 For now, we just have `post-checkout` and `post-merge`,
6 both of which just update submodules,
7 so make sure that you have a fully synced repo whenever you checkout or pull.
8
9 To use these hooks, you can symlink or copy them to your `.git/hooks` directory.
10
11 ln -s ../../git-hooks/post-checkout .git/hooks/post-checkout
12 ln -s ../../git-hooks/post-merge .git/hooks/post-merge
13
@@ -0,0 +1,4 b''
1 #!/bin/sh
2
3 git submodule init
4 git submodule update
@@ -0,0 +1,4 b''
1 #!/bin/sh
2
3 git submodule init
4 git submodule update
@@ -95,6 +95,26 b' def catch_config_error(method, app, *args, **kwargs):'
95 class ApplicationError(Exception):
95 class ApplicationError(Exception):
96 pass
96 pass
97
97
98 class LevelFormatter(logging.Formatter):
99 """Formatter with additional `highlevel` record
100
101 This field is empty if log level is less than highlevel_limit,
102 otherwise it is formatted with self.highlevel_format.
103
104 Useful for adding 'WARNING' to warning messages,
105 without adding 'INFO' to info, etc.
106 """
107 highlevel_limit = logging.WARN
108 highlevel_format = " %(levelname)s |"
109
110 def format(self, record):
111 if record.levelno >= self.highlevel_limit:
112 record.highlevel = self.highlevel_format % record.__dict__
113 else:
114 record.highlevel = ""
115
116 return super(LevelFormatter, self).format(record)
117
98
118
99 class Application(SingletonConfigurable):
119 class Application(SingletonConfigurable):
100 """A singleton application with full configuration support."""
120 """A singleton application with full configuration support."""
@@ -133,13 +153,19 b' class Application(SingletonConfigurable):'
133 self.log_level = new
153 self.log_level = new
134 self.log.setLevel(new)
154 self.log.setLevel(new)
135
155
136 log_format = Unicode("[%(name)s] %(message)s", config=True,
156 log_datefmt = Unicode("%Y-%m-%d %H:%M:%S", config=True,
157 help="The date format used by logging formatters for %(asctime)s"
158 )
159 def _log_datefmt_changed(self, name, old, new):
160 self._log_format_changed()
161
162 log_format = Unicode("[%(name)s]%(highlevel)s %(message)s", config=True,
137 help="The Logging format template",
163 help="The Logging format template",
138 )
164 )
139 def _log_format_changed(self, name, old, new):
165 def _log_format_changed(self, name, old, new):
140 """Change the log formatter when log_format is set."""
166 """Change the log formatter when log_format is set."""
141 _log_handler = self.log.handlers[0]
167 _log_handler = self.log.handlers[0]
142 _log_formatter = logging.Formatter(new)
168 _log_formatter = LevelFormatter(new, datefmt=self._log_datefmt)
143 _log_handler.setFormatter(_log_formatter)
169 _log_handler.setFormatter(_log_formatter)
144
170
145 log = Instance(logging.Logger)
171 log = Instance(logging.Logger)
@@ -167,7 +193,7 b' class Application(SingletonConfigurable):'
167 _log_handler = logging.StreamHandler(open(os.devnull, 'w'))
193 _log_handler = logging.StreamHandler(open(os.devnull, 'w'))
168 else:
194 else:
169 _log_handler = logging.StreamHandler()
195 _log_handler = logging.StreamHandler()
170 _log_formatter = logging.Formatter(self.log_format)
196 _log_formatter = LevelFormatter(self.log_format, datefmt=self.log_datefmt)
171 _log_handler.setFormatter(_log_formatter)
197 _log_handler.setFormatter(_log_formatter)
172 log.addHandler(_log_handler)
198 log.addHandler(_log_handler)
173 return log
199 return log
@@ -6,7 +6,7 b' Authors:'
6 * Brian Granger
6 * Brian Granger
7 """
7 """
8 #-----------------------------------------------------------------------------
8 #-----------------------------------------------------------------------------
9 # Copyright (C) 2008-2011 The IPython Development Team
9 # Copyright (C) 2013 The IPython Development Team
10 #
10 #
11 # Distributed under the terms of the BSD License. The full license is in
11 # Distributed under the terms of the BSD License. The full license is in
12 # the file COPYING, distributed as part of this software.
12 # the file COPYING, distributed as part of this software.
@@ -90,6 +90,7 b' from IPython.kernel.zmq.kernelapp import ('
90 )
90 )
91 from IPython.utils.importstring import import_item
91 from IPython.utils.importstring import import_item
92 from IPython.utils.localinterfaces import LOCALHOST
92 from IPython.utils.localinterfaces import LOCALHOST
93 from IPython.utils import submodule
93 from IPython.utils.traitlets import (
94 from IPython.utils.traitlets import (
94 Dict, Unicode, Integer, List, Enum, Bool,
95 Dict, Unicode, Integer, List, Enum, Bool,
95 DottedObjectName
96 DottedObjectName
@@ -323,7 +324,7 b' class NotebookApp(BaseIPythonApplication):'
323
324
324 def _log_format_default(self):
325 def _log_format_default(self):
325 """override default log format to include time"""
326 """override default log format to include time"""
326 return u"%(asctime)s.%(msecs).03d [%(name)s] %(message)s"
327 return u"%(asctime)s.%(msecs).03d [%(name)s]%(highlevel)s %(message)s"
327
328
328 # create requested profiles by default, if they don't exist:
329 # create requested profiles by default, if they don't exist:
329 auto_create = Bool(True)
330 auto_create = Bool(True)
@@ -537,10 +538,6 b' class NotebookApp(BaseIPythonApplication):'
537 # and all of its ancenstors until propagate is set to False.
538 # and all of its ancenstors until propagate is set to False.
538 self.log.propagate = False
539 self.log.propagate = False
539
540
540 # set the date format
541 formatter = logging.Formatter(self.log_format, datefmt="%Y-%m-%d %H:%M:%S")
542 self.log.handlers[0].setFormatter(formatter)
543
544 # hook up tornado 3's loggers to our app handlers
541 # hook up tornado 3's loggers to our app handlers
545 for name in ('access', 'application', 'general'):
542 for name in ('access', 'application', 'general'):
546 logging.getLogger('tornado.%s' % name).handlers = self.log.handlers
543 logging.getLogger('tornado.%s' % name).handlers = self.log.handlers
@@ -673,11 +670,23 b' class NotebookApp(BaseIPythonApplication):'
673 def _signal_info(self, sig, frame):
670 def _signal_info(self, sig, frame):
674 print self.notebook_info()
671 print self.notebook_info()
675
672
673 def init_components(self):
674 """Check the components submodule, and warn if it's unclean"""
675 status = submodule.check_submodule_status()
676 if status == 'missing':
677 self.log.warn("components submodule missing, running `git submodule update`")
678 submodule.update_submodules(submodule.ipython_parent())
679 elif status == 'unclean':
680 self.log.warn("components submodule unclean, you may see 404s on static/components")
681 self.log.warn("run `setup.py submodule` or `git submodule update` to update")
682
683
676 @catch_config_error
684 @catch_config_error
677 def initialize(self, argv=None):
685 def initialize(self, argv=None):
678 self.init_logging()
686 self.init_logging()
679 super(NotebookApp, self).initialize(argv)
687 super(NotebookApp, self).initialize(argv)
680 self.init_configurables()
688 self.init_configurables()
689 self.init_components()
681 self.init_webapp()
690 self.init_webapp()
682 self.init_signal()
691 self.init_signal()
683
692
@@ -307,10 +307,6 b' class IPKernelApp(BaseIPythonApplication, InteractiveShellApp):'
307 self.hb_port = self.heartbeat.port
307 self.hb_port = self.heartbeat.port
308 self.log.debug("Heartbeat REP Channel on port: %i" % self.hb_port)
308 self.log.debug("Heartbeat REP Channel on port: %i" % self.hb_port)
309 self.heartbeat.start()
309 self.heartbeat.start()
310
311 # Helper to make it easier to connect to an existing kernel.
312 # set log-level to critical, to make sure it is output
313 self.log.critical("To connect another client to this kernel, use:")
314
310
315 def log_connection_info(self):
311 def log_connection_info(self):
316 """display connection info, and store ports"""
312 """display connection info, and store ports"""
@@ -323,8 +319,20 b' class IPKernelApp(BaseIPythonApplication, InteractiveShellApp):'
323 tail += " --profile %s" % self.profile
319 tail += " --profile %s" % self.profile
324 else:
320 else:
325 tail = self.connection_file
321 tail = self.connection_file
326 self.log.critical("--existing %s", tail)
322 lines = [
327
323 "To connect another client to this kernel, use:",
324 " --existing %s" % tail,
325 ]
326 # log connection info
327 # info-level, so often not shown.
328 # frontends should use the %connect_info magic
329 # to see the connection info
330 for line in lines:
331 self.log.info(line)
332 # also raw print to the terminal if no parent (`ipython kernel`)
333 if not self.parent:
334 for line in lines:
335 io.rprint(line)
328
336
329 self.ports = dict(shell=self.shell_port, iopub=self.iopub_port,
337 self.ports = dict(shell=self.shell_port, iopub=self.iopub_port,
330 stdin=self.stdin_port, hb=self.hb_port,
338 stdin=self.stdin_port, hb=self.hb_port,
@@ -29,7 +29,7 b' import sys'
29
29
30 from subprocess import Popen, PIPE
30 from subprocess import Popen, PIPE
31
31
32 from IPython.config.application import catch_config_error
32 from IPython.config.application import catch_config_error, LevelFormatter
33 from IPython.core import release
33 from IPython.core import release
34 from IPython.core.crashhandler import CrashHandler
34 from IPython.core.crashhandler import CrashHandler
35 from IPython.core.application import (
35 from IPython.core.application import (
@@ -105,7 +105,7 b' class BaseParallelApplication(BaseIPythonApplication):'
105
105
106 def _log_format_default(self):
106 def _log_format_default(self):
107 """override default log format to include time"""
107 """override default log format to include time"""
108 return u"%(asctime)s.%(msecs).03d [%(name)s] %(message)s"
108 return u"%(asctime)s.%(msecs).03d [%(name)s]%(highlevel)s %(message)s"
109
109
110 work_dir = Unicode(os.getcwdu(), config=True,
110 work_dir = Unicode(os.getcwdu(), config=True,
111 help='Set the working dir for the process.'
111 help='Set the working dir for the process.'
@@ -191,8 +191,8 b' class BaseParallelApplication(BaseIPythonApplication):'
191 else:
191 else:
192 self._log_handler = self.log.handlers[0]
192 self._log_handler = self.log.handlers[0]
193 # Add timestamps to log format:
193 # Add timestamps to log format:
194 self._log_formatter = logging.Formatter(self.log_format,
194 self._log_formatter = LevelFormatter(self.log_format,
195 datefmt="%Y-%m-%d %H:%M:%S")
195 datefmt=self.log_datefmt)
196 self._log_handler.setFormatter(self._log_formatter)
196 self._log_handler.setFormatter(self._log_formatter)
197 # do not propagate log messages to root logger
197 # do not propagate log messages to root logger
198 # ipcluster app will sometimes print duplicate messages during shutdown
198 # ipcluster app will sometimes print duplicate messages during shutdown
@@ -47,3 +47,18 b' environment so that you can work on your local repo copy and run it from anywher'
47
47
48 The same process applies for other parts, such as the qtconsole (the
48 The same process applies for other parts, such as the qtconsole (the
49 ``extras_require`` attribute in the setup.py file lists all the possibilities).
49 ``extras_require`` attribute in the setup.py file lists all the possibilities).
50
51 Git Hooks and Submodules
52 ************************
53
54 IPython now uses git submodules to ship its javascript dependencies.
55 If you run IPython from git master, you may need to update submodules once in a while with::
56
57 $ git submodule update
58
59 or::
60
61 $ python setup.py submodule
62
63 We have some git hooks for helping keep your submodules always in sync,
64 see our ``git-hooks`` directory for more info.
@@ -68,7 +68,7 b' from setupbase import ('
68 find_data_files,
68 find_data_files,
69 check_for_dependencies,
69 check_for_dependencies,
70 git_prebuild,
70 git_prebuild,
71 check_for_submodules,
71 check_submodule_status,
72 update_submodules,
72 update_submodules,
73 require_submodules,
73 require_submodules,
74 UpdateSubmodules,
74 UpdateSubmodules,
@@ -112,21 +112,37 b" if os_name == 'windows' and 'sdist' in sys.argv:"
112 #-------------------------------------------------------------------------------
112 #-------------------------------------------------------------------------------
113 # Make sure we aren't trying to run without submodules
113 # Make sure we aren't trying to run without submodules
114 #-------------------------------------------------------------------------------
114 #-------------------------------------------------------------------------------
115 here = os.path.abspath(os.path.dirname(__file__))
115
116
116 def ensure_submodules_exist():
117 def require_clean_submodules():
117 """Check out git submodules before distutils can do anything
118 """Check on git submodules before distutils can do anything
118
119
119 Because distutils cannot be trusted to update the tree
120 Since distutils cannot be trusted to update the tree
120 after everything has been set in motion.
121 after everything has been set in motion,
122 this is not a distutils command.
121 """
123 """
122 # don't do anything if nothing is actually supposed to happen
124 # don't do anything if nothing is actually supposed to happen
123 for do_nothing in ('-h', '--help', '--help-commands', 'clean'):
125 for do_nothing in ('-h', '--help', '--help-commands', 'clean', 'submodule'):
124 if do_nothing in sys.argv:
126 if do_nothing in sys.argv:
125 return
127 return
126 if not check_for_submodules():
127 update_submodules()
128
128
129 ensure_submodules_exist()
129 status = check_submodule_status(here)
130
131 if status == "missing":
132 print("checking out submodules for the first time")
133 update_submodules(here)
134 elif status == "unclean":
135 print('\n'.join([
136 "Cannot build / install IPython with unclean submodules",
137 "Please update submodules with",
138 " python setup.py submodule",
139 "or",
140 " git submodule update",
141 "or commit any submodule changes you have made."
142 ]))
143 sys.exit(1)
144
145 require_clean_submodules()
130
146
131 #-------------------------------------------------------------------------------
147 #-------------------------------------------------------------------------------
132 # Things related to the IPython documentation
148 # Things related to the IPython documentation
@@ -373,27 +373,10 b' def check_for_dependencies():'
373 # VCS related
373 # VCS related
374 #---------------------------------------------------------------------------
374 #---------------------------------------------------------------------------
375
375
376 def check_for_submodules():
376 here = os.path.abspath(os.path.dirname(__file__))
377 """return False if there are any submodules that need to be checked out,
378 True otherwise.
379
380 This doesn't check if they are up to date, only existence.
381 """
382 here = os.path.dirname(__file__)
383 submodules = [
384 os.path.join(here, 'IPython', 'frontend', 'html', 'notebook', 'static', 'components')
385 ]
386 for submodule in submodules:
387 if not os.path.exists(submodule):
388 return False
389 return True
390
377
391 def update_submodules():
378 # utils.submodule has checks for submodule status
392 """update git submodules"""
379 execfile(pjoin('IPython','utils','submodule.py'), globals())
393 import subprocess
394 print("updating git submodules")
395 subprocess.check_call('git submodule init'.split())
396 subprocess.check_call('git submodule update --recursive'.split())
397
380
398 class UpdateSubmodules(Command):
381 class UpdateSubmodules(Command):
399 """Update git submodules
382 """Update git submodules
@@ -418,12 +401,10 b' class UpdateSubmodules(Command):'
418 failure = e
401 failure = e
419 print(e)
402 print(e)
420
403
421 if not check_for_submodules():
404 if not check_submodule_status(here) == 'clean':
422 print("submodules could not be checked out")
405 print("submodules could not be checked out")
423 sys.exit(1)
406 sys.exit(1)
424
407
425 # re-scan package data after update
426 self.distribution.package_data = find_package_data()
427
408
428 def git_prebuild(pkg_dir, build_cmd=build_py):
409 def git_prebuild(pkg_dir, build_cmd=build_py):
429 """Return extended build or sdist command class for recording commit
410 """Return extended build or sdist command class for recording commit
@@ -438,10 +419,6 b' def git_prebuild(pkg_dir, build_cmd=build_py):'
438 class MyBuildPy(build_cmd):
419 class MyBuildPy(build_cmd):
439 ''' Subclass to write commit data into installation tree '''
420 ''' Subclass to write commit data into installation tree '''
440 def run(self):
421 def run(self):
441 if not check_for_submodules():
442 print("submodules missing! Run `setup.py submodule` and try again")
443 sys.exit(1)
444
445 build_cmd.run(self)
422 build_cmd.run(self)
446 # this one will only fire for build commands
423 # this one will only fire for build commands
447 if hasattr(self, 'build_lib'):
424 if hasattr(self, 'build_lib'):
@@ -478,14 +455,14 b' def git_prebuild(pkg_dir, build_cmd=build_py):'
478 '# GENERATED BY setup.py\n',
455 '# GENERATED BY setup.py\n',
479 'commit = "%s"\n' % repo_commit,
456 'commit = "%s"\n' % repo_commit,
480 ])
457 ])
481 return MyBuildPy
458 return require_submodules(MyBuildPy)
482
459
483
460
484 def require_submodules(command):
461 def require_submodules(command):
485 """decorator for instructing a command to check for submodules before running"""
462 """decorator for instructing a command to check for submodules before running"""
486 class DecoratedCommand(command):
463 class DecoratedCommand(command):
487 def run(self):
464 def run(self):
488 if not check_for_submodules():
465 if not check_submodule_status(here) == 'clean':
489 print("submodules missing! Run `setup.py submodule` and try again")
466 print("submodules missing! Run `setup.py submodule` and try again")
490 sys.exit(1)
467 sys.exit(1)
491 command.run(self)
468 command.run(self)
General Comments 0
You need to be logged in to leave comments. Login now