1# Copyright (c) 2012 Google Inc. All rights reserved. 2# Use of this source code is governed by a BSD-style license that can be 3# found in the LICENSE file. 4 5"""Visual Studio user preferences file writer.""" 6 7import os 8import re 9import socket # for gethostname 10 11import gyp.easy_xml as easy_xml 12 13 14# ------------------------------------------------------------------------------ 15 16 17def _FindCommandInPath(command): 18 """If there are no slashes in the command given, this function 19 searches the PATH env to find the given command, and converts it 20 to an absolute path. We have to do this because MSVS is looking 21 for an actual file to launch a debugger on, not just a command 22 line. Note that this happens at GYP time, so anything needing to 23 be built needs to have a full path.""" 24 if "/" in command or "\\" in command: 25 # If the command already has path elements (either relative or 26 # absolute), then assume it is constructed properly. 27 return command 28 else: 29 # Search through the path list and find an existing file that 30 # we can access. 31 paths = os.environ.get("PATH", "").split(os.pathsep) 32 for path in paths: 33 item = os.path.join(path, command) 34 if os.path.isfile(item) and os.access(item, os.X_OK): 35 return item 36 return command 37 38 39def _QuoteWin32CommandLineArgs(args): 40 new_args = [] 41 for arg in args: 42 # Replace all double-quotes with double-double-quotes to escape 43 # them for cmd shell, and then quote the whole thing if there 44 # are any. 45 if arg.find('"') != -1: 46 arg = '""'.join(arg.split('"')) 47 arg = '"%s"' % arg 48 49 # Otherwise, if there are any spaces, quote the whole arg. 50 elif re.search(r"[ \t\n]", arg): 51 arg = '"%s"' % arg 52 new_args.append(arg) 53 return new_args 54 55 56class Writer: 57 """Visual Studio XML user user file writer.""" 58 59 def __init__(self, user_file_path, version, name): 60 """Initializes the user file. 61 62 Args: 63 user_file_path: Path to the user file. 64 version: Version info. 65 name: Name of the user file. 66 """ 67 self.user_file_path = user_file_path 68 self.version = version 69 self.name = name 70 self.configurations = {} 71 72 def AddConfig(self, name): 73 """Adds a configuration to the project. 74 75 Args: 76 name: Configuration name. 77 """ 78 self.configurations[name] = ["Configuration", {"Name": name}] 79 80 def AddDebugSettings( 81 self, config_name, command, environment={}, working_directory="" 82 ): 83 """Adds a DebugSettings node to the user file for a particular config. 84 85 Args: 86 command: command line to run. First element in the list is the 87 executable. All elements of the command will be quoted if 88 necessary. 89 working_directory: other files which may trigger the rule. (optional) 90 """ 91 command = _QuoteWin32CommandLineArgs(command) 92 93 abs_command = _FindCommandInPath(command[0]) 94 95 if environment and isinstance(environment, dict): 96 env_list = [f'{key}="{val}"' for (key, val) in environment.items()] 97 environment = " ".join(env_list) 98 else: 99 environment = "" 100 101 n_cmd = [ 102 "DebugSettings", 103 { 104 "Command": abs_command, 105 "WorkingDirectory": working_directory, 106 "CommandArguments": " ".join(command[1:]), 107 "RemoteMachine": socket.gethostname(), 108 "Environment": environment, 109 "EnvironmentMerge": "true", 110 # Currently these are all "dummy" values that we're just setting 111 # in the default manner that MSVS does it. We could use some of 112 # these to add additional capabilities, I suppose, but they might 113 # not have parity with other platforms then. 114 "Attach": "false", 115 "DebuggerType": "3", # 'auto' debugger 116 "Remote": "1", 117 "RemoteCommand": "", 118 "HttpUrl": "", 119 "PDBPath": "", 120 "SQLDebugging": "", 121 "DebuggerFlavor": "0", 122 "MPIRunCommand": "", 123 "MPIRunArguments": "", 124 "MPIRunWorkingDirectory": "", 125 "ApplicationCommand": "", 126 "ApplicationArguments": "", 127 "ShimCommand": "", 128 "MPIAcceptMode": "", 129 "MPIAcceptFilter": "", 130 }, 131 ] 132 133 # Find the config, and add it if it doesn't exist. 134 if config_name not in self.configurations: 135 self.AddConfig(config_name) 136 137 # Add the DebugSettings onto the appropriate config. 138 self.configurations[config_name].append(n_cmd) 139 140 def WriteIfChanged(self): 141 """Writes the user file.""" 142 configs = ["Configurations"] 143 for config, spec in sorted(self.configurations.items()): 144 configs.append(spec) 145 146 content = [ 147 "VisualStudioUserFile", 148 {"Version": self.version.ProjectVersion(), "Name": self.name}, 149 configs, 150 ] 151 easy_xml.WriteXmlIfChanged( 152 content, self.user_file_path, encoding="Windows-1252" 153 ) 154