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