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