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 |
|
---|
7 | import gyp.easy_xml as easy_xml
|
---|
8 |
|
---|
9 | # ------------------------------------------------------------------------------
|
---|
10 |
|
---|
11 |
|
---|
12 | class 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 |
|
---|
34 | class 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 |
|
---|
51 | class 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")
|
---|