Skip to content

Commit

Permalink
Add the vcproj prettifier tool
Browse files Browse the repository at this point in the history
git-svn-id: http://gyp.googlecode.com/svn/trunk@139 78cadc50-ecff-11dd-a971-7dbc132099af
  • Loading branch information
[email protected] committed Feb 9, 2009
1 parent 8ae5f27 commit 4787a79
Show file tree
Hide file tree
Showing 2 changed files with 286 additions and 0 deletions.
15 changes: 15 additions & 0 deletions tools/README
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
pretty_vcproj:
Usage: pretty_vcproj.py "c:\path\to\vcproj.vcproj" [key1=value1] [key2=value2]

They key/value pair are used to resolve vsprops name.

For example, if I want to diff the base.vcproj project:

pretty_vcproj.py z:\dev\src-chrome\src\base\build\base.vcproj "$(SolutionDir)=z:\dev\src-chrome\src\chrome\\" "$(CHROMIUM_BUILD)=" "$(CHROME_BUILD_TYPE)=" > orignal.txt
pretty_vcproj.py z:\dev\src-chrome\src\base\base_gyp.vcproj "$(SolutionDir)=z:\dev\src-chrome\src\chrome\\" "$(CHROMIUM_BUILD)=" "$(CHROME_BUILD_TYPE)=" > gyp.txt

And you can use your favorite diff tool to see the changes.

Note: In the case of base.vcproj, the original vcproj is one level up the generated one.
I suggest you do a search and replace for '"..\' and replace it with '"' in original.txt
before you perform the diff.
271 changes: 271 additions & 0 deletions tools/pretty_vcproj.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,271 @@
#!/usr/bin/python2.5
# Copyright 2009 Google Inc.
# All Rights Reserved.

"""Make the format of a vcproj really pretty.
This script normalize and sort an xml. It also fetches all the properties
inside linked vsprops and include them explicitly in the vcproj.
It outputs the resulting xml to stdout.
"""

__author__ = 'nsylvain (Nicolas Sylvain)'

import os
import sys

from xml.dom.minidom import parse
from xml.dom.minidom import Node

REPLACEMENTS = dict()

class CmpTuple:
"""Compare function between 2 tuple."""
def __call__(self, x, y):
(key1, value1) = x
(key2, value2) = y
return cmp(key1, key2)

class CmpNode:
"""Compare function between 2 xml nodes."""

def get_string(self, node):
node_string = "node"
node_string += node.nodeName
if node.nodeValue:
node_string += node.nodeValue

if node.attributes:
# We first sort by name, if present.
node_string += node.getAttribute("Name")

all_nodes = []
for (name, value) in node.attributes.items():
all_nodes.append((name, value))

all_nodes.sort(CmpTuple())
for (name, value) in all_nodes:
node_string += name
node_string += value

return node_string

def __call__(self, x, y):
return cmp(self.get_string(x), self.get_string(y))

def PrettyPrintNode(node, indent=0):
if node.nodeType == Node.TEXT_NODE:
if node.data.strip():
print '%s%s' % (' '*indent, node.data.strip())
return

if node.childNodes:
node.normalize()
# Get the number of attributes
attr_count = 0
if node.attributes:
attr_count = node.attributes.length

# Print the main tag
if attr_count == 0:
print '%s<%s>' % (' '*indent, node.nodeName)
else:
print '%s<%s' % (' '*indent, node.nodeName)

all_attributes = []
for (name, value) in node.attributes.items():
all_attributes.append((name, value))
all_attributes.sort(CmpTuple())
for (name, value) in all_attributes:
print '%s %s="%s"' % (' '*indent, name, value)
print '%s>' % (' '*indent)
if node.nodeValue:
print '%s %s' % (' '*indent, node.nodeValue)

for sub_node in node.childNodes:
PrettyPrintNode(sub_node, indent=indent+2)
print '%s</%s>' % (' '*indent, node.nodeName)

def CleanupVcproj(node):
# For each sub node, we call recursively this function.
for sub_node in node.childNodes:
CleanupVcproj(sub_node)

# Normalize the node, and remove all extranous whitespaces.
for sub_node in node.childNodes:
if sub_node.nodeType == Node.TEXT_NODE:
sub_node.data = sub_node.data.replace("\r", "")
sub_node.data = sub_node.data.replace("\n", "")
sub_node.data = sub_node.data.rstrip()

# Fix all the semicolon separated attributes to be sorted.
if node.attributes:
for (name, value) in node.attributes.items():
node.setAttribute(name, ';'.join(sorted(value.split(';'))))
if not value:
node.removeAttribute(name)

if node.childNodes:
node.normalize()

# For each node, take a copy, and remove it from the list.
node_array = []
while node.childNodes and node.childNodes[0]:
node_array.append(node.childNodes[0])
node.removeChild(node.childNodes[0])

# Sort the list.
node_array.sort(CmpNode())

# Insert the nodes in the correct order.
for new_node in node_array:
# But don't append empty tool node.
if new_node.nodeName == 'Tool':
if new_node.attributes and new_node.attributes.length == 1:
# This one was empty.
continue
# We also don't add the "_excluded_files" filter.
if new_node.nodeName == 'Filter':
if (new_node.attributes and
new_node.getAttribute('Name') == '_excluded_files'):
continue
node.appendChild(new_node)

def GetConfiguationNodes(vcproj):
#TODO(nsylvain): Find a better way to navigate the xml.
nodes = []
for node in vcproj.childNodes:
if node.nodeName == "Configurations":
for sub_node in node.childNodes:
if sub_node.nodeName == "Configuration":
nodes.append(sub_node)

return nodes

def FixFilenames(filenames, current_directory):
replacements = REPLACEMENTS
#{
# '$(SolutionDir)': 'z:\\dev\\src-chrome\\src\\chrome\\',
# '$(CHROMIUM_BUILD)' :'',
# '$(CHROME_BUILD_TYPE)' :'',
# }

new_list = []
for filename in filenames:
if filename:
for key in replacements:
filename = filename.replace(key, replacements[key])
os.chdir(current_directory)
new_list.append(os.path.abspath(filename))
return new_list

def GetChildrenVsprops(filename):
dom = parse(filename)
if dom.documentElement.attributes:
vsprops = dom.documentElement.getAttribute('InheritedPropertySheets')
return FixFilenames(vsprops.split(';'), os.path.dirname(filename))
return []

def SeekToNode(node1, child2):
# A text node does not have properties.
if child2.nodeType == Node.TEXT_NODE:
return None

# Get the name of the current node.
current_name = child2.getAttribute("Name")
if not current_name:
# There is no name. We don't know how to merge.
return None

# Look through all the nodes to find a match.
for sub_node in node1.childNodes:
if sub_node.nodeName == child2.nodeName:
name = sub_node.getAttribute("Name")
if name == current_name:
return sub_node

# No match. We give up.
return None

def MergeAttributes(node1, node2):
# No attributes to merge?
if not node2.attributes:
return

for (name, value2) in node2.attributes.items():
# Don't merge the 'Name' attribute.
if name == 'Name':
continue
value1 = node1.getAttribute(name)
if value1:
# The attribute exist in the main node. If it's equal, we leave it
# untouched, otherwise we concatenate it.
if value1 != value2:
node1.setAttribute(name, ';'.join([value1, value2]))
else:
# The attribute does nto exist in the main node. We append this one.
node1.setAttribute(name, value2)

# If the attribute was a property sheet attributes, we remove it, since
# they are useless.
if name == 'InheritedPropertySheets':
node1.removeAttribute(name)

def MergeProperties(node1, node2):
MergeAttributes(node1, node2)
for child2 in node2.childNodes:
child1 = SeekToNode(node1, child2)
if child1:
MergeProperties(child1, child2)
else:
node1.appendChild(child2.cloneNode(True))

def main():
"""Main function of this vcproj prettifier."""

# check if we have exactly 1 parameter.
if len(sys.argv) < 2:
print ('Usage: %s "c:\\path\\to\\vcproj.vcproj" [key1=value1] '
'[key2=value2]' % sys.argv[0])
return

# Parse the keys
for i in range(2, len(sys.argv)):
print sys.argv[i].split('=')
(key, value) = sys.argv[i].split('=')
REPLACEMENTS[key] = value

# Open the vcproj and parse the xml.
dom = parse(sys.argv[1])

# First thing we need to do is find the Configuration Node and merge them
# with the vsprops they include.
for configuration_node in GetConfiguationNodes(dom.documentElement):
# Get the property sheets associated with this configuration.
vsprops = configuration_node.getAttribute('InheritedPropertySheets')

# Fix the filenames to be absolute.
vsprops_list = FixFilenames(vsprops.strip().split(';'),
os.path.dirname(sys.argv[1]))

# Extend the list of vsprops with all vsprops contained in the current
# vsprops.
for current_vsprops in vsprops_list:
vsprops_list.extend(GetChildrenVsprops(current_vsprops))

# Now that we have all the vsprops, we need to merge them.
for current_vsprops in vsprops_list:
MergeProperties(configuration_node,
parse(current_vsprops).documentElement)

# Now that everything is merged, we need to cleanup the xml.
CleanupVcproj(dom.documentElement)

# Finally, we use the prett xml function to print the vcproj back to the
# user.
#print dom.toprettyxml(newl="\n")
PrettyPrintNode(dom.documentElement)

if __name__ == '__main__':
main()

0 comments on commit 4787a79

Please sign in to comment.