Home | History | Annotate | Download | only in gyp
      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