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 project reader/writer.""" 6 7import gyp.common 8import gyp.easy_xml as easy_xml 9 10#------------------------------------------------------------------------------ 11 12 13class Tool(object): 14 """Visual Studio tool.""" 15 16 def __init__(self, name, attrs=None): 17 """Initializes the tool. 18 19 Args: 20 name: Tool name. 21 attrs: Dict of tool attributes; may be None. 22 """ 23 self._attrs = attrs or {} 24 self._attrs['Name'] = name 25 26 def _GetSpecification(self): 27 """Creates an element for the tool. 28 29 Returns: 30 A new xml.dom.Element for the tool. 31 """ 32 return ['Tool', self._attrs] 33 34class Filter(object): 35 """Visual Studio filter - that is, a virtual folder.""" 36 37 def __init__(self, name, contents=None): 38 """Initializes the folder. 39 40 Args: 41 name: Filter (folder) name. 42 contents: List of filenames and/or Filter objects contained. 43 """ 44 self.name = name 45 self.contents = list(contents or []) 46 47 48#------------------------------------------------------------------------------ 49 50 51class Writer(object): 52 """Visual Studio XML project writer.""" 53 54 def __init__(self, project_path, version, name, guid=None, platforms=None): 55 """Initializes the project. 56 57 Args: 58 project_path: Path to the project file. 59 version: Format version to emit. 60 name: Name of the project. 61 guid: GUID to use for project, if not None. 62 platforms: Array of string, the supported platforms. If null, ['Win32'] 63 """ 64 self.project_path = project_path 65 self.version = version 66 self.name = name 67 self.guid = guid 68 69 # Default to Win32 for platforms. 70 if not platforms: 71 platforms = ['Win32'] 72 73 # Initialize the specifications of the various sections. 74 self.platform_section = ['Platforms'] 75 for platform in platforms: 76 self.platform_section.append(['Platform', {'Name': platform}]) 77 self.tool_files_section = ['ToolFiles'] 78 self.configurations_section = ['Configurations'] 79 self.files_section = ['Files'] 80 81 # Keep a dict keyed on filename to speed up access. 82 self.files_dict = dict() 83 84 def AddToolFile(self, path): 85 """Adds a tool file to the project. 86 87 Args: 88 path: Relative path from project to tool file. 89 """ 90 self.tool_files_section.append(['ToolFile', {'RelativePath': path}]) 91 92 def _GetSpecForConfiguration(self, config_type, config_name, attrs, tools): 93 """Returns the specification for a configuration. 94 95 Args: 96 config_type: Type of configuration node. 97 config_name: Configuration name. 98 attrs: Dict of configuration attributes; may be None. 99 tools: List of tools (strings or Tool objects); may be None. 100 Returns: 101 """ 102 # Handle defaults 103 if not attrs: 104 attrs = {} 105 if not tools: 106 tools = [] 107 108 # Add configuration node and its attributes 109 node_attrs = attrs.copy() 110 node_attrs['Name'] = config_name 111 specification = [config_type, node_attrs] 112 113 # Add tool nodes and their attributes 114 if tools: 115 for t in tools: 116 if isinstance(t, Tool): 117 specification.append(t._GetSpecification()) 118 else: 119 specification.append(Tool(t)._GetSpecification()) 120 return specification 121 122 123 def AddConfig(self, name, attrs=None, tools=None): 124 """Adds a configuration to the project. 125 126 Args: 127 name: Configuration name. 128 attrs: Dict of configuration attributes; may be None. 129 tools: List of tools (strings or Tool objects); may be None. 130 """ 131 spec = self._GetSpecForConfiguration('Configuration', name, attrs, tools) 132 self.configurations_section.append(spec) 133 134 def _AddFilesToNode(self, parent, files): 135 """Adds files and/or filters to the parent node. 136 137 Args: 138 parent: Destination node 139 files: A list of Filter objects and/or relative paths to files. 140 141 Will call itself recursively, if the files list contains Filter objects. 142 """ 143 for f in files: 144 if isinstance(f, Filter): 145 node = ['Filter', {'Name': f.name}] 146 self._AddFilesToNode(node, f.contents) 147 else: 148 node = ['File', {'RelativePath': f}] 149 self.files_dict[f] = node 150 parent.append(node) 151 152 def AddFiles(self, files): 153 """Adds files to the project. 154 155 Args: 156 files: A list of Filter objects and/or relative paths to files. 157 158 This makes a copy of the file/filter tree at the time of this call. If you 159 later add files to a Filter object which was passed into a previous call 160 to AddFiles(), it will not be reflected in this project. 161 """ 162 self._AddFilesToNode(self.files_section, files) 163 # TODO(rspangler) This also doesn't handle adding files to an existing 164 # filter. That is, it doesn't merge the trees. 165 166 def AddFileConfig(self, path, config, attrs=None, tools=None): 167 """Adds a configuration to a file. 168 169 Args: 170 path: Relative path to the file. 171 config: Name of configuration to add. 172 attrs: Dict of configuration attributes; may be None. 173 tools: List of tools (strings or Tool objects); may be None. 174 175 Raises: 176 ValueError: Relative path does not match any file added via AddFiles(). 177 """ 178 # Find the file node with the right relative path 179 parent = self.files_dict.get(path) 180 if not parent: 181 raise ValueError('AddFileConfig: file "%s" not in project.' % path) 182 183 # Add the config to the file node 184 spec = self._GetSpecForConfiguration('FileConfiguration', config, attrs, 185 tools) 186 parent.append(spec) 187 188 def WriteIfChanged(self): 189 """Writes the project file.""" 190 # First create XML content definition 191 content = [ 192 'VisualStudioProject', 193 {'ProjectType': 'Visual C++', 194 'Version': self.version.ProjectVersion(), 195 'Name': self.name, 196 'ProjectGUID': self.guid, 197 'RootNamespace': self.name, 198 'Keyword': 'Win32Proj' 199 }, 200 self.platform_section, 201 self.tool_files_section, 202 self.configurations_section, 203 ['References'], # empty section 204 self.files_section, 205 ['Globals'] # empty section 206 ] 207 easy_xml.WriteXmlIfChanged(content, self.project_path, 208 encoding="Windows-1252") 209