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.
5 """Visual Studio project reader/writer."""
8 import gyp.easy_xml as easy_xml
10 #------------------------------------------------------------------------------
14 """Visual Studio tool."""
16 def __init__(self, name, attrs=None):
17 """Initializes the tool.
21 attrs: Dict of tool attributes; may be None.
23 self._attrs = attrs or {}
24 self._attrs['Name'] = name
26 def _GetSpecification(self):
27 """Creates an element for the tool.
30 A new xml.dom.Element for the tool.
32 return ['Tool', self._attrs]
35 """Visual Studio filter - that is, a virtual folder."""
37 def __init__(self, name, contents=None):
38 """Initializes the folder.
41 name: Filter (folder) name.
42 contents: List of filenames and/or Filter objects contained.
45 self.contents = list(contents or [])
48 #------------------------------------------------------------------------------
52 """Visual Studio XML project writer."""
54 def __init__(self, project_path, version, name, guid=None, platforms=None):
55 """Initializes the project.
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']
64 self.project_path = project_path
65 self.version = version
69 # Default to Win32 for platforms.
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']
81 # Keep a dict keyed on filename to speed up access.
82 self.files_dict = dict()
84 def AddToolFile(self, path):
85 """Adds a tool file to the project.
88 path: Relative path from project to tool file.
90 self.tool_files_section.append(['ToolFile', {'RelativePath': path}])
92 def _GetSpecForConfiguration(self, config_type, config_name, attrs, tools):
93 """Returns the specification for a configuration.
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.
108 # Add configuration node and its attributes
109 node_attrs = attrs.copy()
110 node_attrs['Name'] = config_name
111 specification = [config_type, node_attrs]
113 # Add tool nodes and their attributes
116 if isinstance(t, Tool):
117 specification.append(t._GetSpecification())
119 specification.append(Tool(t)._GetSpecification())
123 def AddConfig(self, name, attrs=None, tools=None):
124 """Adds a configuration to the project.
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.
131 spec = self._GetSpecForConfiguration('Configuration', name, attrs, tools)
132 self.configurations_section.append(spec)
134 def _AddFilesToNode(self, parent, files):
135 """Adds files and/or filters to the parent node.
138 parent: Destination node
139 files: A list of Filter objects and/or relative paths to files.
141 Will call itself recursively, if the files list contains Filter objects.
144 if isinstance(f, Filter):
145 node = ['Filter', {'Name': f.name}]
146 self._AddFilesToNode(node, f.contents)
148 node = ['File', {'RelativePath': f}]
149 self.files_dict[f] = node
152 def AddFiles(self, files):
153 """Adds files to the project.
156 files: A list of Filter objects and/or relative paths to files.
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.
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.
166 def AddFileConfig(self, path, config, attrs=None, tools=None):
167 """Adds a configuration to a file.
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.
176 ValueError: Relative path does not match any file added via AddFiles().
178 # Find the file node with the right relative path
179 parent = self.files_dict.get(path)
181 raise ValueError('AddFileConfig: file "%s" not in project.' % path)
183 # Add the config to the file node
184 spec = self._GetSpecForConfiguration('FileConfiguration', config, attrs,
188 def WriteIfChanged(self):
189 """Writes the project file."""
190 # First create XML content definition
192 'VisualStudioProject',
193 {'ProjectType': 'Visual C++',
194 'Version': self.version.ProjectVersion(),
196 'ProjectGUID': self.guid,
197 'RootNamespace': self.name,
198 'Keyword': 'Win32Proj'
200 self.platform_section,
201 self.tool_files_section,
202 self.configurations_section,
203 ['References'], # empty section
205 ['Globals'] # empty section
207 easy_xml.WriteXmlIfChanged(content, self.project_path,
208 encoding="Windows-1252")