11cb0ef41Sopenharmony_ci# Copyright (c) 2012 Google Inc. All rights reserved.
21cb0ef41Sopenharmony_ci# Use of this source code is governed by a BSD-style license that can be
31cb0ef41Sopenharmony_ci# found in the LICENSE file.
41cb0ef41Sopenharmony_ci
51cb0ef41Sopenharmony_ci"""Visual Studio project reader/writer."""
61cb0ef41Sopenharmony_ci
71cb0ef41Sopenharmony_ciimport gyp.easy_xml as easy_xml
81cb0ef41Sopenharmony_ci
91cb0ef41Sopenharmony_ci# ------------------------------------------------------------------------------
101cb0ef41Sopenharmony_ci
111cb0ef41Sopenharmony_ci
121cb0ef41Sopenharmony_ciclass Tool:
131cb0ef41Sopenharmony_ci    """Visual Studio tool."""
141cb0ef41Sopenharmony_ci
151cb0ef41Sopenharmony_ci    def __init__(self, name, attrs=None):
161cb0ef41Sopenharmony_ci        """Initializes the tool.
171cb0ef41Sopenharmony_ci
181cb0ef41Sopenharmony_ci    Args:
191cb0ef41Sopenharmony_ci      name: Tool name.
201cb0ef41Sopenharmony_ci      attrs: Dict of tool attributes; may be None.
211cb0ef41Sopenharmony_ci    """
221cb0ef41Sopenharmony_ci        self._attrs = attrs or {}
231cb0ef41Sopenharmony_ci        self._attrs["Name"] = name
241cb0ef41Sopenharmony_ci
251cb0ef41Sopenharmony_ci    def _GetSpecification(self):
261cb0ef41Sopenharmony_ci        """Creates an element for the tool.
271cb0ef41Sopenharmony_ci
281cb0ef41Sopenharmony_ci    Returns:
291cb0ef41Sopenharmony_ci      A new xml.dom.Element for the tool.
301cb0ef41Sopenharmony_ci    """
311cb0ef41Sopenharmony_ci        return ["Tool", self._attrs]
321cb0ef41Sopenharmony_ci
331cb0ef41Sopenharmony_ci
341cb0ef41Sopenharmony_ciclass Filter:
351cb0ef41Sopenharmony_ci    """Visual Studio filter - that is, a virtual folder."""
361cb0ef41Sopenharmony_ci
371cb0ef41Sopenharmony_ci    def __init__(self, name, contents=None):
381cb0ef41Sopenharmony_ci        """Initializes the folder.
391cb0ef41Sopenharmony_ci
401cb0ef41Sopenharmony_ci    Args:
411cb0ef41Sopenharmony_ci      name: Filter (folder) name.
421cb0ef41Sopenharmony_ci      contents: List of filenames and/or Filter objects contained.
431cb0ef41Sopenharmony_ci    """
441cb0ef41Sopenharmony_ci        self.name = name
451cb0ef41Sopenharmony_ci        self.contents = list(contents or [])
461cb0ef41Sopenharmony_ci
471cb0ef41Sopenharmony_ci
481cb0ef41Sopenharmony_ci# ------------------------------------------------------------------------------
491cb0ef41Sopenharmony_ci
501cb0ef41Sopenharmony_ci
511cb0ef41Sopenharmony_ciclass Writer:
521cb0ef41Sopenharmony_ci    """Visual Studio XML project writer."""
531cb0ef41Sopenharmony_ci
541cb0ef41Sopenharmony_ci    def __init__(self, project_path, version, name, guid=None, platforms=None):
551cb0ef41Sopenharmony_ci        """Initializes the project.
561cb0ef41Sopenharmony_ci
571cb0ef41Sopenharmony_ci    Args:
581cb0ef41Sopenharmony_ci      project_path: Path to the project file.
591cb0ef41Sopenharmony_ci      version: Format version to emit.
601cb0ef41Sopenharmony_ci      name: Name of the project.
611cb0ef41Sopenharmony_ci      guid: GUID to use for project, if not None.
621cb0ef41Sopenharmony_ci      platforms: Array of string, the supported platforms.  If null, ['Win32']
631cb0ef41Sopenharmony_ci    """
641cb0ef41Sopenharmony_ci        self.project_path = project_path
651cb0ef41Sopenharmony_ci        self.version = version
661cb0ef41Sopenharmony_ci        self.name = name
671cb0ef41Sopenharmony_ci        self.guid = guid
681cb0ef41Sopenharmony_ci
691cb0ef41Sopenharmony_ci        # Default to Win32 for platforms.
701cb0ef41Sopenharmony_ci        if not platforms:
711cb0ef41Sopenharmony_ci            platforms = ["Win32"]
721cb0ef41Sopenharmony_ci
731cb0ef41Sopenharmony_ci        # Initialize the specifications of the various sections.
741cb0ef41Sopenharmony_ci        self.platform_section = ["Platforms"]
751cb0ef41Sopenharmony_ci        for platform in platforms:
761cb0ef41Sopenharmony_ci            self.platform_section.append(["Platform", {"Name": platform}])
771cb0ef41Sopenharmony_ci        self.tool_files_section = ["ToolFiles"]
781cb0ef41Sopenharmony_ci        self.configurations_section = ["Configurations"]
791cb0ef41Sopenharmony_ci        self.files_section = ["Files"]
801cb0ef41Sopenharmony_ci
811cb0ef41Sopenharmony_ci        # Keep a dict keyed on filename to speed up access.
821cb0ef41Sopenharmony_ci        self.files_dict = dict()
831cb0ef41Sopenharmony_ci
841cb0ef41Sopenharmony_ci    def AddToolFile(self, path):
851cb0ef41Sopenharmony_ci        """Adds a tool file to the project.
861cb0ef41Sopenharmony_ci
871cb0ef41Sopenharmony_ci    Args:
881cb0ef41Sopenharmony_ci      path: Relative path from project to tool file.
891cb0ef41Sopenharmony_ci    """
901cb0ef41Sopenharmony_ci        self.tool_files_section.append(["ToolFile", {"RelativePath": path}])
911cb0ef41Sopenharmony_ci
921cb0ef41Sopenharmony_ci    def _GetSpecForConfiguration(self, config_type, config_name, attrs, tools):
931cb0ef41Sopenharmony_ci        """Returns the specification for a configuration.
941cb0ef41Sopenharmony_ci
951cb0ef41Sopenharmony_ci    Args:
961cb0ef41Sopenharmony_ci      config_type: Type of configuration node.
971cb0ef41Sopenharmony_ci      config_name: Configuration name.
981cb0ef41Sopenharmony_ci      attrs: Dict of configuration attributes; may be None.
991cb0ef41Sopenharmony_ci      tools: List of tools (strings or Tool objects); may be None.
1001cb0ef41Sopenharmony_ci    Returns:
1011cb0ef41Sopenharmony_ci    """
1021cb0ef41Sopenharmony_ci        # Handle defaults
1031cb0ef41Sopenharmony_ci        if not attrs:
1041cb0ef41Sopenharmony_ci            attrs = {}
1051cb0ef41Sopenharmony_ci        if not tools:
1061cb0ef41Sopenharmony_ci            tools = []
1071cb0ef41Sopenharmony_ci
1081cb0ef41Sopenharmony_ci        # Add configuration node and its attributes
1091cb0ef41Sopenharmony_ci        node_attrs = attrs.copy()
1101cb0ef41Sopenharmony_ci        node_attrs["Name"] = config_name
1111cb0ef41Sopenharmony_ci        specification = [config_type, node_attrs]
1121cb0ef41Sopenharmony_ci
1131cb0ef41Sopenharmony_ci        # Add tool nodes and their attributes
1141cb0ef41Sopenharmony_ci        if tools:
1151cb0ef41Sopenharmony_ci            for t in tools:
1161cb0ef41Sopenharmony_ci                if isinstance(t, Tool):
1171cb0ef41Sopenharmony_ci                    specification.append(t._GetSpecification())
1181cb0ef41Sopenharmony_ci                else:
1191cb0ef41Sopenharmony_ci                    specification.append(Tool(t)._GetSpecification())
1201cb0ef41Sopenharmony_ci        return specification
1211cb0ef41Sopenharmony_ci
1221cb0ef41Sopenharmony_ci    def AddConfig(self, name, attrs=None, tools=None):
1231cb0ef41Sopenharmony_ci        """Adds a configuration to the project.
1241cb0ef41Sopenharmony_ci
1251cb0ef41Sopenharmony_ci    Args:
1261cb0ef41Sopenharmony_ci      name: Configuration name.
1271cb0ef41Sopenharmony_ci      attrs: Dict of configuration attributes; may be None.
1281cb0ef41Sopenharmony_ci      tools: List of tools (strings or Tool objects); may be None.
1291cb0ef41Sopenharmony_ci    """
1301cb0ef41Sopenharmony_ci        spec = self._GetSpecForConfiguration("Configuration", name, attrs, tools)
1311cb0ef41Sopenharmony_ci        self.configurations_section.append(spec)
1321cb0ef41Sopenharmony_ci
1331cb0ef41Sopenharmony_ci    def _AddFilesToNode(self, parent, files):
1341cb0ef41Sopenharmony_ci        """Adds files and/or filters to the parent node.
1351cb0ef41Sopenharmony_ci
1361cb0ef41Sopenharmony_ci    Args:
1371cb0ef41Sopenharmony_ci      parent: Destination node
1381cb0ef41Sopenharmony_ci      files: A list of Filter objects and/or relative paths to files.
1391cb0ef41Sopenharmony_ci
1401cb0ef41Sopenharmony_ci    Will call itself recursively, if the files list contains Filter objects.
1411cb0ef41Sopenharmony_ci    """
1421cb0ef41Sopenharmony_ci        for f in files:
1431cb0ef41Sopenharmony_ci            if isinstance(f, Filter):
1441cb0ef41Sopenharmony_ci                node = ["Filter", {"Name": f.name}]
1451cb0ef41Sopenharmony_ci                self._AddFilesToNode(node, f.contents)
1461cb0ef41Sopenharmony_ci            else:
1471cb0ef41Sopenharmony_ci                node = ["File", {"RelativePath": f}]
1481cb0ef41Sopenharmony_ci                self.files_dict[f] = node
1491cb0ef41Sopenharmony_ci            parent.append(node)
1501cb0ef41Sopenharmony_ci
1511cb0ef41Sopenharmony_ci    def AddFiles(self, files):
1521cb0ef41Sopenharmony_ci        """Adds files to the project.
1531cb0ef41Sopenharmony_ci
1541cb0ef41Sopenharmony_ci    Args:
1551cb0ef41Sopenharmony_ci      files: A list of Filter objects and/or relative paths to files.
1561cb0ef41Sopenharmony_ci
1571cb0ef41Sopenharmony_ci    This makes a copy of the file/filter tree at the time of this call.  If you
1581cb0ef41Sopenharmony_ci    later add files to a Filter object which was passed into a previous call
1591cb0ef41Sopenharmony_ci    to AddFiles(), it will not be reflected in this project.
1601cb0ef41Sopenharmony_ci    """
1611cb0ef41Sopenharmony_ci        self._AddFilesToNode(self.files_section, files)
1621cb0ef41Sopenharmony_ci        # TODO(rspangler) This also doesn't handle adding files to an existing
1631cb0ef41Sopenharmony_ci        # filter.  That is, it doesn't merge the trees.
1641cb0ef41Sopenharmony_ci
1651cb0ef41Sopenharmony_ci    def AddFileConfig(self, path, config, attrs=None, tools=None):
1661cb0ef41Sopenharmony_ci        """Adds a configuration to a file.
1671cb0ef41Sopenharmony_ci
1681cb0ef41Sopenharmony_ci    Args:
1691cb0ef41Sopenharmony_ci      path: Relative path to the file.
1701cb0ef41Sopenharmony_ci      config: Name of configuration to add.
1711cb0ef41Sopenharmony_ci      attrs: Dict of configuration attributes; may be None.
1721cb0ef41Sopenharmony_ci      tools: List of tools (strings or Tool objects); may be None.
1731cb0ef41Sopenharmony_ci
1741cb0ef41Sopenharmony_ci    Raises:
1751cb0ef41Sopenharmony_ci      ValueError: Relative path does not match any file added via AddFiles().
1761cb0ef41Sopenharmony_ci    """
1771cb0ef41Sopenharmony_ci        # Find the file node with the right relative path
1781cb0ef41Sopenharmony_ci        parent = self.files_dict.get(path)
1791cb0ef41Sopenharmony_ci        if not parent:
1801cb0ef41Sopenharmony_ci            raise ValueError('AddFileConfig: file "%s" not in project.' % path)
1811cb0ef41Sopenharmony_ci
1821cb0ef41Sopenharmony_ci        # Add the config to the file node
1831cb0ef41Sopenharmony_ci        spec = self._GetSpecForConfiguration("FileConfiguration", config, attrs, tools)
1841cb0ef41Sopenharmony_ci        parent.append(spec)
1851cb0ef41Sopenharmony_ci
1861cb0ef41Sopenharmony_ci    def WriteIfChanged(self):
1871cb0ef41Sopenharmony_ci        """Writes the project file."""
1881cb0ef41Sopenharmony_ci        # First create XML content definition
1891cb0ef41Sopenharmony_ci        content = [
1901cb0ef41Sopenharmony_ci            "VisualStudioProject",
1911cb0ef41Sopenharmony_ci            {
1921cb0ef41Sopenharmony_ci                "ProjectType": "Visual C++",
1931cb0ef41Sopenharmony_ci                "Version": self.version.ProjectVersion(),
1941cb0ef41Sopenharmony_ci                "Name": self.name,
1951cb0ef41Sopenharmony_ci                "ProjectGUID": self.guid,
1961cb0ef41Sopenharmony_ci                "RootNamespace": self.name,
1971cb0ef41Sopenharmony_ci                "Keyword": "Win32Proj",
1981cb0ef41Sopenharmony_ci            },
1991cb0ef41Sopenharmony_ci            self.platform_section,
2001cb0ef41Sopenharmony_ci            self.tool_files_section,
2011cb0ef41Sopenharmony_ci            self.configurations_section,
2021cb0ef41Sopenharmony_ci            ["References"],  # empty section
2031cb0ef41Sopenharmony_ci            self.files_section,
2041cb0ef41Sopenharmony_ci            ["Globals"],  # empty section
2051cb0ef41Sopenharmony_ci        ]
2061cb0ef41Sopenharmony_ci        easy_xml.WriteXmlIfChanged(content, self.project_path, encoding="Windows-1252")
207