1# Copyright 2016 The Chromium Authors 2# Use of this source code is governed by a BSD-style license that can be 3# found in the LICENSE file. 4 5"""Helper functions for gcc_toolchain.gni wrappers.""" 6 7import gzip 8import os 9import re 10import subprocess 11import shlex 12import shutil 13import sys 14import threading 15 16import whole_archive 17 18_BAT_PREFIX = 'cmd /c call ' 19 20 21def _GzipThenDelete(src_path, dest_path): 22 # Results for Android map file with GCC on a z620: 23 # Uncompressed: 207MB 24 # gzip -9: 16.4MB, takes 8.7 seconds. 25 # gzip -1: 21.8MB, takes 2.0 seconds. 26 # Piping directly from the linker via -print-map (or via -Map with a fifo) 27 # adds a whopping 30-45 seconds! 28 with open(src_path, 'rb') as f_in, gzip.GzipFile(dest_path, 'wb', 1) as f_out: 29 shutil.copyfileobj(f_in, f_out) 30 os.unlink(src_path) 31 32 33def CommandToRun(command): 34 """Generates commands compatible with Windows. 35 36 When running on a Windows host and using a toolchain whose tools are 37 actually wrapper scripts (i.e. .bat files on Windows) rather than binary 38 executables, the |command| to run has to be prefixed with this magic. 39 The GN toolchain definitions take care of that for when GN/Ninja is 40 running the tool directly. When that command is passed in to this 41 script, it appears as a unitary string but needs to be split up so that 42 just 'cmd' is the actual command given to Python's subprocess module. 43 44 Args: 45 command: List containing the UNIX style |command|. 46 47 Returns: 48 A list containing the Windows version of the |command|. 49 """ 50 if command[0].startswith(_BAT_PREFIX): 51 command = command[0].split(None, 3) + command[1:] 52 return command 53 54 55def RunLinkWithOptionalMapFile(command, env=None, map_file=None): 56 """Runs the given command, adding in -Wl,-Map when |map_file| is given. 57 58 Also takes care of gzipping when |map_file| ends with .gz. 59 60 Args: 61 command: List of arguments comprising the command. 62 env: Environment variables. 63 map_file: Path to output map_file. 64 65 Returns: 66 The exit code of running |command|. 67 """ 68 tmp_map_path = None 69 if map_file and map_file.endswith('.gz'): 70 tmp_map_path = map_file + '.tmp' 71 command.append('-Wl,-Map,' + tmp_map_path) 72 elif map_file: 73 command.append('-Wl,-Map,' + map_file) 74 75 # We want to link rlibs as --whole-archive if they are part of a unit test 76 # target. This is determined by switch `-LinkWrapper,add-whole-archive`. 77 command = whole_archive.wrap_with_whole_archive(command) 78 79 result = subprocess.call(command, env=env) 80 81 if tmp_map_path and result == 0: 82 threading.Thread( 83 target=lambda: _GzipThenDelete(tmp_map_path, map_file)).start() 84 elif tmp_map_path and os.path.exists(tmp_map_path): 85 os.unlink(tmp_map_path) 86 87 return result 88 89 90def CaptureCommandStderr(command, env=None): 91 """Returns the stderr of a command. 92 93 Args: 94 command: A list containing the command and arguments. 95 env: Environment variables for the new process. 96 """ 97 child = subprocess.Popen(command, stderr=subprocess.PIPE, env=env) 98 _, stderr = child.communicate() 99 return child.returncode, stderr 100