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