• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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