Switched to 'oem' encoding per jcbrill's advice
[scons.git] / SCons / Platform / win32.py
blob548f99e0fa7b70ffd1f0367fbffb8c5103df7581
1 # MIT License
3 # Copyright The SCons Foundation
5 # Permission is hereby granted, free of charge, to any person obtaining
6 # a copy of this software and associated documentation files (the
7 # "Software"), to deal in the Software without restriction, including
8 # without limitation the rights to use, copy, modify, merge, publish,
9 # distribute, sublicense, and/or sell copies of the Software, and to
10 # permit persons to whom the Software is furnished to do so, subject to
11 # the following conditions:
13 # The above copyright notice and this permission notice shall be included
14 # in all copies or substantial portions of the Software.
16 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
17 # KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
18 # WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19 # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20 # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21 # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22 # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
24 """Platform-specific initialization for Win32 systems.
26 There normally shouldn't be any need to import this module directly. It
27 will usually be imported through the generic SCons.Platform.Platform()
28 selection method.
29 """
31 import os
32 import os.path
33 import platform
34 import sys
35 import tempfile
37 from SCons.Platform.posix import exitvalmap
38 from SCons.Platform import TempFileMunge
39 from SCons.Platform.virtualenv import ImportVirtualenv
40 from SCons.Platform.virtualenv import ignore_virtualenv, enable_virtualenv
41 import SCons.Util
43 CHOCO_DEFAULT_PATH = [
44 r'C:\ProgramData\chocolatey\bin'
47 if False:
48 # Now swap out shutil.filecopy and filecopy2 for win32 api native CopyFile
49 try:
50 from ctypes import windll
51 import shutil
53 CopyFile = windll.kernel32.CopyFileA
54 SetFileTime = windll.kernel32.SetFileTime
56 _shutil_copy = shutil.copy
57 _shutil_copy2 = shutil.copy2
59 shutil.copy2 = CopyFile
61 def win_api_copyfile(src,dst) -> None:
62 CopyFile(src,dst)
63 os.utime(dst)
65 shutil.copy = win_api_copyfile
67 except AttributeError:
68 parallel_msg = \
69 "Couldn't override shutil.copy or shutil.copy2 falling back to shutil defaults"
77 try:
78 import threading
79 spawn_lock = threading.Lock()
81 # This locked version of spawnve works around a Windows
82 # MSVCRT bug, because its spawnve is not thread-safe.
83 # Without this, python can randomly crash while using -jN.
84 # See the python bug at https://github.com/python/cpython/issues/50725
85 # and SCons issue at https://github.com/SCons/scons/issues/2449
86 def spawnve(mode, file, args, env):
87 spawn_lock.acquire()
88 try:
89 if mode == os.P_WAIT:
90 ret = os.spawnve(os.P_NOWAIT, file, args, env)
91 else:
92 ret = os.spawnve(mode, file, args, env)
93 finally:
94 spawn_lock.release()
95 if mode == os.P_WAIT:
96 pid, status = os.waitpid(ret, 0)
97 ret = status >> 8
98 return ret
99 except ImportError:
100 # Use the unsafe method of spawnve.
101 # Please, don't try to optimize this try-except block
102 # away by assuming that the threading module is always present.
103 # In the test test/option-j.py we intentionally call SCons with
104 # a fake threading.py that raises an import exception right away,
105 # simulating a non-existent package.
106 def spawnve(mode, file, args, env):
107 return os.spawnve(mode, file, args, env)
109 # The upshot of all this is that, if you are using Python 1.5.2,
110 # you had better have cmd or command.com in your PATH when you run
111 # scons.
114 def piped_spawn(sh, escape, cmd, args, env, stdout, stderr):
115 # There is no direct way to do that in python. What we do
116 # here should work for most cases:
117 # In case stdout (stderr) is not redirected to a file,
118 # we redirect it into a temporary file tmpFileStdout
119 # (tmpFileStderr) and copy the contents of this file
120 # to stdout (stderr) given in the argument
121 # Note that because this will paste shell redirection syntax
122 # into the cmdline, we have to call a shell to run the command,
123 # even though that's a bit of a performance hit.
124 if not sh:
125 sys.stderr.write("scons: Could not find command interpreter, is it in your PATH?\n")
126 return 127
128 # one temporary file for stdout and stderr
129 tmpFileStdout, tmpFileStdoutName = tempfile.mkstemp(text=True)
130 os.close(tmpFileStdout) # don't need open until the subproc is done
131 tmpFileStderr, tmpFileStderrName = tempfile.mkstemp(text=True)
132 os.close(tmpFileStderr)
134 # check if output is redirected
135 stdoutRedirected = False
136 stderrRedirected = False
137 for arg in args:
138 # are there more possibilities to redirect stdout ?
139 if arg.find(">", 0, 1) != -1 or arg.find("1>", 0, 2) != -1:
140 stdoutRedirected = True
141 # are there more possibilities to redirect stderr ?
142 if arg.find("2>", 0, 2) != -1:
143 stderrRedirected = True
145 # redirect output of non-redirected streams to our tempfiles
146 if not stdoutRedirected:
147 args.append(">" + tmpFileStdoutName)
148 if not stderrRedirected:
149 args.append("2>" + tmpFileStderrName)
151 # Sanitize encoding. None is not a valid encoding.
152 # Since we're handling a redirected shell command use
153 # the shells default encoding.
154 if stdout.encoding is None:
155 stdout.encoding = 'oem'
156 if stderr.encoding is None:
157 stderr.encoding = 'oem'
159 # actually do the spawn
160 try:
161 args = [sh, '/C', escape(' '.join(args))]
162 ret = spawnve(os.P_WAIT, sh, args, env)
163 except OSError as e:
164 # catch any error
165 try:
166 ret = exitvalmap[e.errno]
167 except KeyError:
168 sys.stderr.write("scons: unknown OSError exception code %d - %s: %s\n" % (e.errno, cmd, e.strerror))
169 if stderr is not None:
170 stderr.write("scons: %s: %s\n" % (cmd, e.strerror))
172 # copy child output from tempfiles to our streams
173 # and do clean up stuff
174 if stdout is not None and not stdoutRedirected:
175 try:
176 with open(tmpFileStdoutName, "rb") as tmpFileStdout:
177 output = tmpFileStdout.read()
178 stdout.write(output.decode(stdout.encoding, "replace"))
179 os.remove(tmpFileStdoutName)
180 except OSError:
181 pass
183 if stderr is not None and not stderrRedirected:
184 try:
185 with open(tmpFileStderrName, "rb") as tmpFileStderr:
186 errors = tmpFileStderr.read()
187 stderr.write(errors.decode(stderr.encoding, "replace"))
188 os.remove(tmpFileStderrName)
189 except OSError:
190 pass
192 return ret
195 def exec_spawn(l, env):
196 try:
197 result = spawnve(os.P_WAIT, l[0], l, env)
198 except OSError as e:
199 try:
200 result = exitvalmap[e.errno]
201 sys.stderr.write("scons: %s: %s\n" % (l[0], e.strerror))
202 except KeyError:
203 result = 127
204 if len(l) > 2:
205 if len(l[2]) < 1000:
206 command = ' '.join(l[0:3])
207 else:
208 command = l[0]
209 else:
210 command = l[0]
211 sys.stderr.write("scons: unknown OSError exception code %d - '%s': %s\n" % (e.errno, command, e.strerror))
212 return result
215 def spawn(sh, escape, cmd, args, env):
216 if not sh:
217 sys.stderr.write("scons: Could not find command interpreter, is it in your PATH?\n")
218 return 127
219 return exec_spawn([sh, '/C', escape(' '.join(args))], env)
221 # Windows does not allow special characters in file names anyway, so no
222 # need for a complex escape function, we will just quote the arg, except
223 # that "cmd /c" requires that if an argument ends with a backslash it
224 # needs to be escaped so as not to interfere with closing double quote
225 # that we add.
226 def escape(x):
227 if x[-1] == '\\':
228 x = x + '\\'
229 return '"' + x + '"'
231 # Get the windows system directory name
232 _system_root = None
235 def get_system_root():
236 global _system_root
237 if _system_root is not None:
238 return _system_root
240 # A resonable default if we can't read the registry
241 val = os.environ.get('SystemRoot', "C:\\WINDOWS")
243 if SCons.Util.can_read_reg:
244 try:
245 # Look for Windows NT system root
246 k=SCons.Util.RegOpenKeyEx(SCons.Util.hkey_mod.HKEY_LOCAL_MACHINE,
247 'Software\\Microsoft\\Windows NT\\CurrentVersion')
248 val, tok = SCons.Util.RegQueryValueEx(k, 'SystemRoot')
249 except SCons.Util.RegError:
250 try:
251 # Okay, try the Windows 9x system root
252 k=SCons.Util.RegOpenKeyEx(SCons.Util.hkey_mod.HKEY_LOCAL_MACHINE,
253 'Software\\Microsoft\\Windows\\CurrentVersion')
254 val, tok = SCons.Util.RegQueryValueEx(k, 'SystemRoot')
255 except KeyboardInterrupt:
256 raise
257 except:
258 pass
260 _system_root = val
261 return val
264 def get_program_files_dir():
266 Get the location of the program files directory
267 Returns
268 -------
271 # Now see if we can look in the registry...
272 val = ''
273 if SCons.Util.can_read_reg:
274 try:
275 # Look for Windows Program Files directory
276 k=SCons.Util.RegOpenKeyEx(SCons.Util.hkey_mod.HKEY_LOCAL_MACHINE,
277 'Software\\Microsoft\\Windows\\CurrentVersion')
278 val, tok = SCons.Util.RegQueryValueEx(k, 'ProgramFilesDir')
279 except SCons.Util.RegError:
280 val = ''
282 if val == '':
283 # A reasonable default if we can't read the registry
284 # (Actually, it's pretty reasonable even if we can :-)
285 val = os.path.join(os.path.dirname(get_system_root()),"Program Files")
287 return val
290 class ArchDefinition:
292 Determine which windows CPU were running on.
293 A class for defining architecture-specific settings and logic.
295 def __init__(self, arch, synonyms=[]) -> None:
296 self.arch = arch
297 self.synonyms = synonyms
299 SupportedArchitectureList = [
300 ArchDefinition(
301 'x86',
302 ['i386', 'i486', 'i586', 'i686'],
305 ArchDefinition(
306 'x86_64',
307 ['AMD64', 'amd64', 'em64t', 'EM64T', 'x86_64'],
310 ArchDefinition(
311 'arm64',
312 ['ARM64', 'aarch64', 'AARCH64', 'AArch64'],
315 ArchDefinition(
316 'ia64',
317 ['IA64'],
321 SupportedArchitectureMap = {}
322 for a in SupportedArchitectureList:
323 SupportedArchitectureMap[a.arch] = a
324 for s in a.synonyms:
325 SupportedArchitectureMap[s] = a
328 def get_architecture(arch=None):
329 """Returns the definition for the specified architecture string.
331 If no string is specified, the system default is returned (as defined
332 by the registry PROCESSOR_ARCHITECTURE value, PROCESSOR_ARCHITEW6432
333 environment variable, PROCESSOR_ARCHITECTURE environment variable, or
334 the platform machine).
336 if arch is None:
337 if SCons.Util.can_read_reg:
338 try:
339 k=SCons.Util.RegOpenKeyEx(SCons.Util.hkey_mod.HKEY_LOCAL_MACHINE,
340 'SYSTEM\\CurrentControlSet\\Control\\Session Manager\\Environment')
341 val, tok = SCons.Util.RegQueryValueEx(k, 'PROCESSOR_ARCHITECTURE')
342 except SCons.Util.RegError:
343 val = ''
344 if val and val in SupportedArchitectureMap:
345 arch = val
346 if arch is None:
347 arch = os.environ.get('PROCESSOR_ARCHITEW6432')
348 if not arch:
349 arch = os.environ.get('PROCESSOR_ARCHITECTURE')
350 return SupportedArchitectureMap.get(arch, ArchDefinition(platform.machine(), [platform.machine()]))
353 def generate(env):
354 # Attempt to find cmd.exe (for WinNT/2k/XP) or
355 # command.com for Win9x
356 cmd_interp = ''
357 # First see if we can look in the registry...
358 if SCons.Util.can_read_reg:
359 try:
360 # Look for Windows NT system root
361 k=SCons.Util.RegOpenKeyEx(SCons.Util.hkey_mod.HKEY_LOCAL_MACHINE,
362 'Software\\Microsoft\\Windows NT\\CurrentVersion')
363 val, tok = SCons.Util.RegQueryValueEx(k, 'SystemRoot')
364 cmd_interp = os.path.join(val, 'System32\\cmd.exe')
365 except SCons.Util.RegError:
366 try:
367 # Okay, try the Windows 9x system root
368 k=SCons.Util.RegOpenKeyEx(SCons.Util.hkey_mod.HKEY_LOCAL_MACHINE,
369 'Software\\Microsoft\\Windows\\CurrentVersion')
370 val, tok = SCons.Util.RegQueryValueEx(k, 'SystemRoot')
371 cmd_interp = os.path.join(val, 'command.com')
372 except KeyboardInterrupt:
373 raise
374 except:
375 pass
377 # For the special case of not having access to the registry, we
378 # use a temporary path and pathext to attempt to find the command
379 # interpreter. If we fail, we try to find the interpreter through
380 # the env's PATH. The problem with that is that it might not
381 # contain an ENV and a PATH.
382 if not cmd_interp:
383 systemroot = get_system_root()
384 tmp_path = systemroot + os.pathsep + \
385 os.path.join(systemroot,'System32')
386 tmp_pathext = '.com;.exe;.bat;.cmd'
387 if 'PATHEXT' in os.environ:
388 tmp_pathext = os.environ['PATHEXT']
389 cmd_interp = SCons.Util.WhereIs('cmd', tmp_path, tmp_pathext)
390 if not cmd_interp:
391 cmd_interp = SCons.Util.WhereIs('command', tmp_path, tmp_pathext)
393 if not cmd_interp:
394 cmd_interp = env.Detect('cmd')
395 if not cmd_interp:
396 cmd_interp = env.Detect('command')
398 if 'ENV' not in env:
399 env['ENV'] = {}
401 # Import things from the external environment to the construction
402 # environment's ENV. This is a potential slippery slope, because we
403 # *don't* want to make builds dependent on the user's environment by
404 # default. We're doing this for SystemRoot, though, because it's
405 # needed for anything that uses sockets, and seldom changes, and
406 # for SystemDrive because it's related.
408 # Weigh the impact carefully before adding other variables to this list.
409 import_env = ['SystemDrive', 'SystemRoot', 'TEMP', 'TMP', 'USERPROFILE']
410 for var in import_env:
411 v = os.environ.get(var)
412 if v:
413 env['ENV'][var] = v
415 if 'COMSPEC' not in env['ENV']:
416 v = os.environ.get("COMSPEC")
417 if v:
418 env['ENV']['COMSPEC'] = v
420 env.AppendENVPath('PATH', get_system_root() + '\\System32')
422 env['ENV']['PATHEXT'] = '.COM;.EXE;.BAT;.CMD'
423 env['OBJPREFIX'] = ''
424 env['OBJSUFFIX'] = '.obj'
425 env['SHOBJPREFIX'] = '$OBJPREFIX'
426 env['SHOBJSUFFIX'] = '$OBJSUFFIX'
427 env['PROGPREFIX'] = ''
428 env['PROGSUFFIX'] = '.exe'
429 env['LIBPREFIX'] = ''
430 env['LIBSUFFIX'] = '.lib'
431 env['SHLIBPREFIX'] = ''
432 env['SHLIBSUFFIX'] = '.dll'
433 env['LIBPREFIXES'] = ['$LIBPREFIX']
434 env['LIBSUFFIXES'] = ['$LIBSUFFIX']
435 env['LIBLITERALPREFIX'] = ''
436 env['PSPAWN'] = piped_spawn
437 env['SPAWN'] = spawn
438 env['SHELL'] = cmd_interp
439 env['TEMPFILE'] = TempFileMunge
440 env['TEMPFILEPREFIX'] = '@'
441 env['MAXLINELENGTH'] = 2048
442 env['ESCAPE'] = escape
444 env['HOST_OS'] = 'win32'
445 env['HOST_ARCH'] = get_architecture().arch
447 if enable_virtualenv and not ignore_virtualenv:
448 ImportVirtualenv(env)
451 # Local Variables:
452 # tab-width:4
453 # indent-tabs-mode:nil
454 # End:
455 # vim: set expandtab tabstop=4 shiftwidth=4: