• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#!/usr/bin/env python
2
3# Copyright (c) 2012 Google Inc. All rights reserved.
4# Use of this source code is governed by a BSD-style license that can be
5# found in the LICENSE file.
6
7"""Make the format of a vcproj really pretty.
8
9   This script normalize and sort an xml. It also fetches all the properties
10   inside linked vsprops and include them explicitly in the vcproj.
11
12   It outputs the resulting xml to stdout.
13"""
14
15from __future__ import print_function
16
17import os
18import sys
19
20from xml.dom.minidom import parse
21from xml.dom.minidom import Node
22
23__author__ = 'nsylvain (Nicolas Sylvain)'
24
25try:
26  cmp
27except NameError:
28  def cmp(x, y):
29    return (x > y) - (x < y)
30
31REPLACEMENTS = dict()
32ARGUMENTS = None
33
34
35class CmpTuple(object):
36  """Compare function between 2 tuple."""
37  def __call__(self, x, y):
38    return cmp(x[0], y[0])
39
40
41class CmpNode(object):
42  """Compare function between 2 xml nodes."""
43
44  def __call__(self, x, y):
45    def get_string(node):
46      node_string = "node"
47      node_string += node.nodeName
48      if node.nodeValue:
49        node_string += node.nodeValue
50
51      if node.attributes:
52        # We first sort by name, if present.
53        node_string += node.getAttribute("Name")
54
55        all_nodes = []
56        for (name, value) in node.attributes.items():
57          all_nodes.append((name, value))
58
59        all_nodes.sort(CmpTuple())
60        for (name, value) in all_nodes:
61          node_string += name
62          node_string += value
63
64      return node_string
65
66    return cmp(get_string(x), get_string(y))
67
68
69def PrettyPrintNode(node, indent=0):
70  if node.nodeType == Node.TEXT_NODE:
71    if node.data.strip():
72      print('%s%s' % (' '*indent, node.data.strip()))
73    return
74
75  if node.childNodes:
76    node.normalize()
77  # Get the number of attributes
78  attr_count = 0
79  if node.attributes:
80    attr_count = node.attributes.length
81
82  # Print the main tag
83  if attr_count == 0:
84    print('%s<%s>' % (' '*indent, node.nodeName))
85  else:
86    print('%s<%s' % (' '*indent, node.nodeName))
87
88    all_attributes = []
89    for (name, value) in node.attributes.items():
90      all_attributes.append((name, value))
91      all_attributes.sort(CmpTuple())
92    for (name, value) in all_attributes:
93      print('%s  %s="%s"' % (' '*indent, name, value))
94    print('%s>' % (' '*indent))
95  if node.nodeValue:
96    print('%s  %s' % (' '*indent, node.nodeValue))
97
98  for sub_node in node.childNodes:
99    PrettyPrintNode(sub_node, indent=indent+2)
100  print('%s</%s>' % (' '*indent, node.nodeName))
101
102
103def FlattenFilter(node):
104  """Returns a list of all the node and sub nodes."""
105  node_list = []
106
107  if (node.attributes and
108      node.getAttribute('Name') == '_excluded_files'):
109      # We don't add the "_excluded_files" filter.
110    return []
111
112  for current in node.childNodes:
113    if current.nodeName == 'Filter':
114      node_list.extend(FlattenFilter(current))
115    else:
116      node_list.append(current)
117
118  return node_list
119
120
121def FixFilenames(filenames, current_directory):
122  new_list = []
123  for filename in filenames:
124    if filename:
125      for key in REPLACEMENTS:
126        filename = filename.replace(key, REPLACEMENTS[key])
127      os.chdir(current_directory)
128      filename = filename.strip('"\' ')
129      if filename.startswith('$'):
130        new_list.append(filename)
131      else:
132        new_list.append(os.path.abspath(filename))
133  return new_list
134
135
136def AbsoluteNode(node):
137  """Makes all the properties we know about in this node absolute."""
138  if node.attributes:
139    for (name, value) in node.attributes.items():
140      if name in ['InheritedPropertySheets', 'RelativePath',
141                  'AdditionalIncludeDirectories',
142                  'IntermediateDirectory', 'OutputDirectory',
143                  'AdditionalLibraryDirectories']:
144        # We want to fix up these paths
145        path_list = value.split(';')
146        new_list = FixFilenames(path_list, os.path.dirname(ARGUMENTS[1]))
147        node.setAttribute(name, ';'.join(new_list))
148      if not value:
149        node.removeAttribute(name)
150
151
152def CleanupVcproj(node):
153  """For each sub node, we call recursively this function."""
154  for sub_node in node.childNodes:
155    AbsoluteNode(sub_node)
156    CleanupVcproj(sub_node)
157
158  # Normalize the node, and remove all extranous whitespaces.
159  for sub_node in node.childNodes:
160    if sub_node.nodeType == Node.TEXT_NODE:
161      sub_node.data = sub_node.data.replace("\r", "")
162      sub_node.data = sub_node.data.replace("\n", "")
163      sub_node.data = sub_node.data.rstrip()
164
165  # Fix all the semicolon separated attributes to be sorted, and we also
166  # remove the dups.
167  if node.attributes:
168    for (name, value) in node.attributes.items():
169      sorted_list = sorted(value.split(';'))
170      unique_list = []
171      for i in sorted_list:
172        if not unique_list.count(i):
173          unique_list.append(i)
174      node.setAttribute(name, ';'.join(unique_list))
175      if not value:
176        node.removeAttribute(name)
177
178  if node.childNodes:
179    node.normalize()
180
181  # For each node, take a copy, and remove it from the list.
182  node_array = []
183  while node.childNodes and node.childNodes[0]:
184    # Take a copy of the node and remove it from the list.
185    current = node.childNodes[0]
186    node.removeChild(current)
187
188    # If the child is a filter, we want to append all its children
189    # to this same list.
190    if current.nodeName == 'Filter':
191      node_array.extend(FlattenFilter(current))
192    else:
193      node_array.append(current)
194
195
196  # Sort the list.
197  node_array.sort(CmpNode())
198
199  # Insert the nodes in the correct order.
200  for new_node in node_array:
201    # But don't append empty tool node.
202    if new_node.nodeName == 'Tool':
203      if new_node.attributes and new_node.attributes.length == 1:
204        # This one was empty.
205        continue
206    if new_node.nodeName == 'UserMacro':
207      continue
208    node.appendChild(new_node)
209
210
211def GetConfiguationNodes(vcproj):
212  #TODO(nsylvain): Find a better way to navigate the xml.
213  nodes = []
214  for node in vcproj.childNodes:
215    if node.nodeName == "Configurations":
216      for sub_node in node.childNodes:
217        if sub_node.nodeName == "Configuration":
218          nodes.append(sub_node)
219
220  return nodes
221
222
223def GetChildrenVsprops(filename):
224  dom = parse(filename)
225  if dom.documentElement.attributes:
226    vsprops = dom.documentElement.getAttribute('InheritedPropertySheets')
227    return FixFilenames(vsprops.split(';'), os.path.dirname(filename))
228  return []
229
230def SeekToNode(node1, child2):
231  # A text node does not have properties.
232  if child2.nodeType == Node.TEXT_NODE:
233    return None
234
235  # Get the name of the current node.
236  current_name = child2.getAttribute("Name")
237  if not current_name:
238    # There is no name. We don't know how to merge.
239    return None
240
241  # Look through all the nodes to find a match.
242  for sub_node in node1.childNodes:
243    if sub_node.nodeName == child2.nodeName:
244      name = sub_node.getAttribute("Name")
245      if name == current_name:
246        return sub_node
247
248  # No match. We give up.
249  return None
250
251
252def MergeAttributes(node1, node2):
253  # No attributes to merge?
254  if not node2.attributes:
255    return
256
257  for (name, value2) in node2.attributes.items():
258    # Don't merge the 'Name' attribute.
259    if name == 'Name':
260      continue
261    value1 = node1.getAttribute(name)
262    if value1:
263      # The attribute exist in the main node. If it's equal, we leave it
264      # untouched, otherwise we concatenate it.
265      if value1 != value2:
266        node1.setAttribute(name, ';'.join([value1, value2]))
267    else:
268      # The attribute does not exist in the main node. We append this one.
269      node1.setAttribute(name, value2)
270
271    # If the attribute was a property sheet attributes, we remove it, since
272    # they are useless.
273    if name == 'InheritedPropertySheets':
274      node1.removeAttribute(name)
275
276
277def MergeProperties(node1, node2):
278  MergeAttributes(node1, node2)
279  for child2 in node2.childNodes:
280    child1 = SeekToNode(node1, child2)
281    if child1:
282      MergeProperties(child1, child2)
283    else:
284      node1.appendChild(child2.cloneNode(True))
285
286
287def main(argv):
288  """Main function of this vcproj prettifier."""
289  global ARGUMENTS
290  ARGUMENTS = argv
291
292  # check if we have exactly 1 parameter.
293  if len(argv) < 2:
294    print(('Usage: %s "c:\\path\\to\\vcproj.vcproj" [key1=value1] '
295           '[key2=value2]' % argv[0]))
296    return 1
297
298  # Parse the keys
299  for i in range(2, len(argv)):
300    (key, value) = argv[i].split('=')
301    REPLACEMENTS[key] = value
302
303  # Open the vcproj and parse the xml.
304  dom = parse(argv[1])
305
306  # First thing we need to do is find the Configuration Node and merge them
307  # with the vsprops they include.
308  for configuration_node in GetConfiguationNodes(dom.documentElement):
309    # Get the property sheets associated with this configuration.
310    vsprops = configuration_node.getAttribute('InheritedPropertySheets')
311
312    # Fix the filenames to be absolute.
313    vsprops_list = FixFilenames(vsprops.strip().split(';'),
314                                os.path.dirname(argv[1]))
315
316    # Extend the list of vsprops with all vsprops contained in the current
317    # vsprops.
318    for current_vsprops in vsprops_list:
319      vsprops_list.extend(GetChildrenVsprops(current_vsprops))
320
321    # Now that we have all the vsprops, we need to merge them.
322    for current_vsprops in vsprops_list:
323      MergeProperties(configuration_node,
324                      parse(current_vsprops).documentElement)
325
326  # Now that everything is merged, we need to cleanup the xml.
327  CleanupVcproj(dom.documentElement)
328
329  # Finally, we use the prett xml function to print the vcproj back to the
330  # user.
331  #print dom.toprettyxml(newl="\n")
332  PrettyPrintNode(dom.documentElement)
333  return 0
334
335
336if __name__ == '__main__':
337  sys.exit(main(sys.argv))
338