##// END OF EJS Templates
logtoprocess: use lowercase for docstring title
Jun Wu -
r31601:8e7feaad default
parent child Browse files
Show More
@@ -1,131 +1,131
1 # logtoprocess.py - send ui.log() data to a subprocess
1 # logtoprocess.py - send ui.log() data to a subprocess
2 #
2 #
3 # Copyright 2016 Facebook, Inc.
3 # Copyright 2016 Facebook, Inc.
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7 """Send ui.log() data to a subprocess (EXPERIMENTAL)
7 """send ui.log() data to a subprocess (EXPERIMENTAL)
8
8
9 This extension lets you specify a shell command per ui.log() event,
9 This extension lets you specify a shell command per ui.log() event,
10 sending all remaining arguments to as environment variables to that command.
10 sending all remaining arguments to as environment variables to that command.
11
11
12 Each positional argument to the method results in a `MSG[N]` key in the
12 Each positional argument to the method results in a `MSG[N]` key in the
13 environment, starting at 1 (so `MSG1`, `MSG2`, etc.). Each keyword argument
13 environment, starting at 1 (so `MSG1`, `MSG2`, etc.). Each keyword argument
14 is set as a `OPT_UPPERCASE_KEY` variable (so the key is uppercased, and
14 is set as a `OPT_UPPERCASE_KEY` variable (so the key is uppercased, and
15 prefixed with `OPT_`). The original event name is passed in the `EVENT`
15 prefixed with `OPT_`). The original event name is passed in the `EVENT`
16 environment variable, and the process ID of mercurial is given in `HGPID`.
16 environment variable, and the process ID of mercurial is given in `HGPID`.
17
17
18 So given a call `ui.log('foo', 'bar', 'baz', spam='eggs'), a script configured
18 So given a call `ui.log('foo', 'bar', 'baz', spam='eggs'), a script configured
19 for the `foo` event can expect an environment with `MSG1=bar`, `MSG2=baz`, and
19 for the `foo` event can expect an environment with `MSG1=bar`, `MSG2=baz`, and
20 `OPT_SPAM=eggs`.
20 `OPT_SPAM=eggs`.
21
21
22 Scripts are configured in the `[logtoprocess]` section, each key an event name.
22 Scripts are configured in the `[logtoprocess]` section, each key an event name.
23 For example::
23 For example::
24
24
25 [logtoprocess]
25 [logtoprocess]
26 commandexception = echo "$MSG2$MSG3" > /var/log/mercurial_exceptions.log
26 commandexception = echo "$MSG2$MSG3" > /var/log/mercurial_exceptions.log
27
27
28 would log the warning message and traceback of any failed command dispatch.
28 would log the warning message and traceback of any failed command dispatch.
29
29
30 Scripts are run asynchronously as detached daemon processes; mercurial will
30 Scripts are run asynchronously as detached daemon processes; mercurial will
31 not ensure that they exit cleanly.
31 not ensure that they exit cleanly.
32
32
33 """
33 """
34
34
35 from __future__ import absolute_import
35 from __future__ import absolute_import
36
36
37 import itertools
37 import itertools
38 import os
38 import os
39 import platform
39 import platform
40 import subprocess
40 import subprocess
41 import sys
41 import sys
42
42
43 from mercurial import encoding
43 from mercurial import encoding
44
44
45 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
45 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
46 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
46 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
47 # be specifying the version(s) of Mercurial they are tested with, or
47 # be specifying the version(s) of Mercurial they are tested with, or
48 # leave the attribute unspecified.
48 # leave the attribute unspecified.
49 testedwith = 'ships-with-hg-core'
49 testedwith = 'ships-with-hg-core'
50
50
51 def uisetup(ui):
51 def uisetup(ui):
52 if platform.system() == 'Windows':
52 if platform.system() == 'Windows':
53 # no fork on Windows, but we can create a detached process
53 # no fork on Windows, but we can create a detached process
54 # https://msdn.microsoft.com/en-us/library/windows/desktop/ms684863.aspx
54 # https://msdn.microsoft.com/en-us/library/windows/desktop/ms684863.aspx
55 # No stdlib constant exists for this value
55 # No stdlib constant exists for this value
56 DETACHED_PROCESS = 0x00000008
56 DETACHED_PROCESS = 0x00000008
57 _creationflags = DETACHED_PROCESS | subprocess.CREATE_NEW_PROCESS_GROUP
57 _creationflags = DETACHED_PROCESS | subprocess.CREATE_NEW_PROCESS_GROUP
58
58
59 def runshellcommand(script, env):
59 def runshellcommand(script, env):
60 # we can't use close_fds *and* redirect stdin. I'm not sure that we
60 # we can't use close_fds *and* redirect stdin. I'm not sure that we
61 # need to because the detached process has no console connection.
61 # need to because the detached process has no console connection.
62 subprocess.Popen(
62 subprocess.Popen(
63 script, shell=True, env=env, close_fds=True,
63 script, shell=True, env=env, close_fds=True,
64 creationflags=_creationflags)
64 creationflags=_creationflags)
65 else:
65 else:
66 def runshellcommand(script, env):
66 def runshellcommand(script, env):
67 # double-fork to completely detach from the parent process
67 # double-fork to completely detach from the parent process
68 # based on http://code.activestate.com/recipes/278731
68 # based on http://code.activestate.com/recipes/278731
69 pid = os.fork()
69 pid = os.fork()
70 if pid:
70 if pid:
71 # parent
71 # parent
72 return
72 return
73 # subprocess.Popen() forks again, all we need to add is
73 # subprocess.Popen() forks again, all we need to add is
74 # flag the new process as a new session.
74 # flag the new process as a new session.
75 if sys.version_info < (3, 2):
75 if sys.version_info < (3, 2):
76 newsession = {'preexec_fn': os.setsid}
76 newsession = {'preexec_fn': os.setsid}
77 else:
77 else:
78 newsession = {'start_new_session': True}
78 newsession = {'start_new_session': True}
79 try:
79 try:
80 # connect stdin to devnull to make sure the subprocess can't
80 # connect stdin to devnull to make sure the subprocess can't
81 # muck up that stream for mercurial.
81 # muck up that stream for mercurial.
82 subprocess.Popen(
82 subprocess.Popen(
83 script, shell=True, stdin=open(os.devnull, 'r'), env=env,
83 script, shell=True, stdin=open(os.devnull, 'r'), env=env,
84 close_fds=True, **newsession)
84 close_fds=True, **newsession)
85 finally:
85 finally:
86 # mission accomplished, this child needs to exit and not
86 # mission accomplished, this child needs to exit and not
87 # continue the hg process here.
87 # continue the hg process here.
88 os._exit(0)
88 os._exit(0)
89
89
90 class logtoprocessui(ui.__class__):
90 class logtoprocessui(ui.__class__):
91 def log(self, event, *msg, **opts):
91 def log(self, event, *msg, **opts):
92 """Map log events to external commands
92 """Map log events to external commands
93
93
94 Arguments are passed on as environment variables.
94 Arguments are passed on as environment variables.
95
95
96 """
96 """
97 script = self.config('logtoprocess', event)
97 script = self.config('logtoprocess', event)
98 if script:
98 if script:
99 if msg:
99 if msg:
100 # try to format the log message given the remaining
100 # try to format the log message given the remaining
101 # arguments
101 # arguments
102 try:
102 try:
103 # Python string formatting with % either uses a
103 # Python string formatting with % either uses a
104 # dictionary *or* tuple, but not both. If we have
104 # dictionary *or* tuple, but not both. If we have
105 # keyword options, assume we need a mapping.
105 # keyword options, assume we need a mapping.
106 formatted = msg[0] % (opts or msg[1:])
106 formatted = msg[0] % (opts or msg[1:])
107 except (TypeError, KeyError):
107 except (TypeError, KeyError):
108 # Failed to apply the arguments, ignore
108 # Failed to apply the arguments, ignore
109 formatted = msg[0]
109 formatted = msg[0]
110 messages = (formatted,) + msg[1:]
110 messages = (formatted,) + msg[1:]
111 else:
111 else:
112 messages = msg
112 messages = msg
113 # positional arguments are listed as MSG[N] keys in the
113 # positional arguments are listed as MSG[N] keys in the
114 # environment
114 # environment
115 msgpairs = (
115 msgpairs = (
116 ('MSG{0:d}'.format(i), str(m))
116 ('MSG{0:d}'.format(i), str(m))
117 for i, m in enumerate(messages, 1))
117 for i, m in enumerate(messages, 1))
118 # keyword arguments get prefixed with OPT_ and uppercased
118 # keyword arguments get prefixed with OPT_ and uppercased
119 optpairs = (
119 optpairs = (
120 ('OPT_{0}'.format(key.upper()), str(value))
120 ('OPT_{0}'.format(key.upper()), str(value))
121 for key, value in opts.iteritems())
121 for key, value in opts.iteritems())
122 env = dict(itertools.chain(encoding.environ.items(),
122 env = dict(itertools.chain(encoding.environ.items(),
123 msgpairs, optpairs),
123 msgpairs, optpairs),
124 EVENT=event, HGPID=str(os.getpid()))
124 EVENT=event, HGPID=str(os.getpid()))
125 # Connect stdin to /dev/null to prevent child processes messing
125 # Connect stdin to /dev/null to prevent child processes messing
126 # with mercurial's stdin.
126 # with mercurial's stdin.
127 runshellcommand(script, env)
127 runshellcommand(script, env)
128 return super(logtoprocessui, self).log(event, *msg, **opts)
128 return super(logtoprocessui, self).log(event, *msg, **opts)
129
129
130 # Replace the class for this instance and all clones created from it:
130 # Replace the class for this instance and all clones created from it:
131 ui.__class__ = logtoprocessui
131 ui.__class__ = logtoprocessui
General Comments 0
You need to be logged in to leave comments. Login now