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.easy_xml as easy_xml 8 9# ------------------------------------------------------------------------------ 10 11 12class Tool(object): 13 """Visual Studio tool.""" 14 15 def __init__(self, name, attrs=None): 16 """Initializes the tool. 17 18 Args: 19 name: Tool name. 20 attrs: Dict of tool attributes; may be None. 21 """ 22 self._attrs = attrs or {} 23 self._attrs["Name"] = name 24 25 def _GetSpecification(self): 26 """Creates an element for the tool. 27 28 Returns: 29 A new xml.dom.Element for the tool. 30 """ 31 return ["Tool", self._attrs] 32 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 def AddConfig(self, name, attrs=None, tools=None): 123 """Adds a configuration to the project. 124 125 Args: 126 name: Configuration name. 127 attrs: Dict of configuration attributes; may be None. 128 tools: List of tools (strings or Tool objects); may be None. 129 """ 130 spec = self._GetSpecForConfiguration("Configuration", name, attrs, tools) 131 self.configurations_section.append(spec) 132 133 def _AddFilesToNode(self, parent, files): 134 """Adds files and/or filters to the parent node. 135 136 Args: 137 parent: Destination node 138 files: A list of Filter objects and/or relative paths to files. 139 140 Will call itself recursively, if the files list contains Filter objects. 141 """ 142 for f in files: 143 if isinstance(f, Filter): 144 node = ["Filter", {"Name": f.name}] 145 self._AddFilesToNode(node, f.contents) 146 else: 147 node = ["File", {"RelativePath": f}] 148 self.files_dict[f] = node 149 parent.append(node) 150 151 def AddFiles(self, files): 152 """Adds files to the project. 153 154 Args: 155 files: A list of Filter objects and/or relative paths to files. 156 157 This makes a copy of the file/filter tree at the time of this call. If you 158 later add files to a Filter object which was passed into a previous call 159 to AddFiles(), it will not be reflected in this project. 160 """ 161 self._AddFilesToNode(self.files_section, files) 162 # TODO(rspangler) This also doesn't handle adding files to an existing 163 # filter. That is, it doesn't merge the trees. 164 165 def AddFileConfig(self, path, config, attrs=None, tools=None): 166 """Adds a configuration to a file. 167 168 Args: 169 path: Relative path to the file. 170 config: Name of configuration to add. 171 attrs: Dict of configuration attributes; may be None. 172 tools: List of tools (strings or Tool objects); may be None. 173 174 Raises: 175 ValueError: Relative path does not match any file added via AddFiles(). 176 """ 177 # Find the file node with the right relative path 178 parent = self.files_dict.get(path) 179 if not parent: 180 raise ValueError('AddFileConfig: file "%s" not in project.' % path) 181 182 # Add the config to the file node 183 spec = self._GetSpecForConfiguration("FileConfiguration", config, attrs, tools) 184 parent.append(spec) 185 186 def WriteIfChanged(self): 187 """Writes the project file.""" 188 # First create XML content definition 189 content = [ 190 "VisualStudioProject", 191 { 192 "ProjectType": "Visual C++", 193 "Version": self.version.ProjectVersion(), 194 "Name": self.name, 195 "ProjectGUID": self.guid, 196 "RootNamespace": self.name, 197 "Keyword": "Win32Proj", 198 }, 199 self.platform_section, 200 self.tool_files_section, 201 self.configurations_section, 202 ["References"], # empty section 203 self.files_section, 204 ["Globals"], # empty section 205 ] 206 easy_xml.WriteXmlIfChanged(content, self.project_path, encoding="Windows-1252") 207