-
Notifications
You must be signed in to change notification settings - Fork 71
/
Copy pathauto_update_vcxproj.py
executable file
·114 lines (95 loc) · 3.3 KB
/
auto_update_vcxproj.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
#!/usr/bin/env python3
import codecs
import sys
import xml.etree.ElementTree as ET
from pathlib import Path
VCXPROJ_NAMESPACES = {
'msbuild': "http://schemas.microsoft.com/developer/msbuild/2003",
}
KNOWN_ITEM_GROUPS = {
'ClCompile': {".c", ".cpp"},
'ClInclude': {".h", ".hpp", ".cuh"},
'CudaCompile': {".cu"},
}
IGNORED_FILENAMES = {
# CMake-specific files
"config.h",
"config_cmake.h",
# macOS-specific files
"mrfiledialogcocoa.h",
"mrtouchpadcocoahandler.h",
}
def add_missing_entries(vcxproj_dir, item_group, tag_name, file_suffixes):
found_files = {
item.attrib['Include'].lower()
for item in item_group.iterfind(f'msbuild:{tag_name}', VCXPROJ_NAMESPACES)
}
for path in vcxproj_dir.iterdir():
name, suffix = path.name.lower(), path.suffix.lower()
if name in IGNORED_FILENAMES:
continue
if suffix in file_suffixes and name not in found_files:
print(f"Appending {tag_name}: Include = {path.name}")
item_group.append(ET.Element(
tag_name,
attrib={
'Include': path.name,
},
))
def fix_vcxproj(source, generated):
"""
patch the generated XML to match the vcxproj format
"""
# preserve BOM
if source.startswith(codecs.BOM_UTF8):
generated = codecs.BOM_UTF8 + generated
# preserve trailing newline
if source.endswith(b'\n'):
generated += b'\n'
# preserve newline format
if source.find(b'\r\n') != -1:
generated = generated.replace(b'\n', b'\r\n')
# fix quotes
generated = generated.replace(
b"<?xml version='1.0' encoding='utf-8'?>",
b'<?xml version="1.0" encoding="utf-8"?>',
1,
)
# fix attribute position
xmlns_attr = b' xmlns="http://schemas.microsoft.com/developer/msbuild/2003"'
xmlns_attr_pos = generated.find(xmlns_attr)
m1, m2, m3 = xmlns_attr_pos, xmlns_attr_pos + len(xmlns_attr), generated.find(b'>', xmlns_attr_pos)
generated = generated[:m1] + generated[m2:m3] + generated[m1:m2] + generated[m3:]
return generated
def process_file(vcxproj_path):
with open(vcxproj_path, 'rb') as f:
input = f.read()
parser = ET.XMLParser(target=ET.TreeBuilder(
insert_comments=True,
))
vcxproj = ET.parse(vcxproj_path, parser)
project = vcxproj.getroot()
for item_group in project.iterfind('msbuild:ItemGroup', VCXPROJ_NAMESPACES):
for tag_name, file_suffixes in KNOWN_ITEM_GROUPS.items():
if item_group.find(f'msbuild:{tag_name}', VCXPROJ_NAMESPACES) is not None:
add_missing_entries(vcxproj_path.parent, item_group, tag_name, file_suffixes)
ET.register_namespace('', VCXPROJ_NAMESPACES['msbuild'])
ET.indent(vcxproj, space=" ")
output = ET.tostring(
project,
encoding='utf-8',
xml_declaration=True,
)
output = fix_vcxproj(input, output)
with open(vcxproj_path, 'wb') as f:
f.write(output)
if __name__ == "__main__":
arg = Path(sys.argv[1])
if arg.is_file() and arg.suffix == '.vcxproj':
process_file(arg)
elif arg.is_dir():
for path in arg.rglob('*.vcxproj'):
process_file(path)
else:
print(f"Unsupported file: {arg}", file=sys.stderr)
sys.exit(1)