Completed
Branch rebuild (e7a2d2)
by Glenn
08:26
created

Writer   A

Complexity

Total Complexity 18

Size/Duplication

Total Lines 158
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
wmc 18
dl 0
loc 158
c 0
b 0
f 0
rs 10
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.common
8
import gyp.easy_xml as easy_xml
9
10
#------------------------------------------------------------------------------
11
12
13
class 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
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
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