updated so TestSCons.NINJA_BINARY is set and used by all such tests
[scons.git] / SCons / Tool / ninja / ninja_run_daemon.py
blob666be0f716d19a21bc6a12050b23eef55391487f
1 #!/usr/bin/env python3
3 # MIT License
5 # Copyright The SCons Foundation
7 # Permission is hereby granted, free of charge, to any person obtaining
8 # a copy of this software and associated documentation files (the
9 # "Software"), to deal in the Software without restriction, including
10 # without limitation the rights to use, copy, modify, merge, publish,
11 # distribute, sublicense, and/or sell copies of the Software, and to
12 # permit persons to whom the Software is furnished to do so, subject to
13 # the following conditions:
15 # The above copyright notice and this permission notice shall be included
16 # in all copies or substantial portions of the Software.
18 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
19 # KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
20 # WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
21 # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
22 # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
23 # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
24 # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
26 """
27 This script is intended to be called by ninja to start up the scons daemon process. It will
28 launch the server and attempt to connect to it. This process needs to completely detach
29 from the spawned process so ninja can consider the build edge completed. It should be passed
30 the args which should be forwarded to the scons daemon process which could be any number of
31 # arguments. However the first few arguments are required to be port, ninja dir, and keep alive
32 timeout in seconds.
34 The scons_daemon_dirty file acts as a pidfile marker letting this script quickly skip over
35 restarting the server if the server is running. The assumption here is the pidfile should only
36 exist if the server is running.
37 """
39 import subprocess
40 import sys
41 import os
42 import pathlib
43 import tempfile
44 import hashlib
45 import logging
46 import time
47 import http.client
48 import traceback
49 import socket
51 ninja_builddir = pathlib.Path(sys.argv[2])
52 daemon_dir = pathlib.Path(tempfile.gettempdir()) / (
53 "scons_daemon_" + str(hashlib.md5(str(ninja_builddir).encode()).hexdigest())
55 os.makedirs(daemon_dir, exist_ok=True)
57 logging.basicConfig(
58 filename=daemon_dir / "scons_daemon.log",
59 filemode="a",
60 format="%(asctime)s %(message)s",
61 level=logging.DEBUG,
64 def log_error(msg) -> None:
65 logging.debug(msg)
66 sys.stderr.write(msg)
68 if not os.path.exists(ninja_builddir / "scons_daemon_dirty"):
69 cmd = [
70 sys.executable,
71 str(pathlib.Path(__file__).parent / "ninja_scons_daemon.py"),
72 ] + sys.argv[1:]
73 logging.debug(f"Starting daemon with {' '.join(cmd)}")
76 # TODO: Remove the following when Python3.6 support is dropped.
77 if sys.platform == 'win32' and sys.version_info[0] == 3 and sys.version_info[1] == 6:
78 # on Windows with Python version 3.6, popen does not do a good job disconnecting
79 # the std handles and this make ninja hang because they stay open to the original
80 # process ninja launched. Here we can force the handles to be separated.
81 # See: https://docs.python.org/3.6/library/subprocess.html#subprocess.STARTUPINFO
82 # See Also: https://docs.python.org/3.6/library/subprocess.html#subprocess.Popen
83 # Note when you don't specify stdin, stdout, and/or stderr they default to None
84 # which indicates no output redirection will occur.
85 si = subprocess.STARTUPINFO()
86 si.dwFlags = subprocess.STARTF_USESTDHANDLES
87 p = subprocess.Popen(
88 cmd, close_fds=True, shell=False, startupinfo=si
90 else:
91 p = subprocess.Popen(
92 cmd, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, shell=False,
94 with open(daemon_dir / "pidfile", "w") as f:
95 f.write(str(p.pid))
96 with open(ninja_builddir / "scons_daemon_dirty", "w") as f:
97 f.write(str(p.pid))
99 error_msg = f"ERROR: Failed to connect to scons daemon.\n Check {daemon_dir / 'scons_daemon.log'} for more info.\n"
101 while True:
102 try:
103 logging.debug("Attempting to connect scons daemon")
104 conn = http.client.HTTPConnection(
105 "127.0.0.1", port=int(sys.argv[1]), timeout=60
107 conn.request("GET", "/?ready=true")
108 response = None
110 try:
111 response = conn.getresponse()
112 except (http.client.RemoteDisconnected, http.client.ResponseNotReady, socket.timeout):
113 time.sleep(0.01)
114 except http.client.HTTPException:
115 log_error(f"Error: {traceback.format_exc()}")
116 exit(1)
117 else:
118 msg = response.read()
119 status = response.status
120 if status != 200:
121 log_error(msg.decode("utf-8"))
122 exit(1)
123 logging.debug("Server Responded it was ready!")
124 break
126 except ConnectionRefusedError:
127 logging.debug(f"Server not ready, server PID: {p.pid}")
128 time.sleep(1)
129 if p.poll() is not None:
130 log_error(f"Server process died, aborting: {p.returncode}")
131 sys.exit(p.returncode)
132 except ConnectionResetError:
133 log_error("Server ConnectionResetError")
134 exit(1)
135 except Exception:
136 log_error(f"Error: {traceback.format_exc()}")
137 exit(1)
139 # Local Variables:
140 # tab-width:4
141 # indent-tabs-mode:nil
142 # End:
143 # vim: set expandtab tabstop=4 shiftwidth=4: