Mercurial > vim-crosoft
annotate scripts/vsutil.py @ 9:4ba6df1b2f97
Add built-in properties to the solution environment before resolving items.
author | Ludovic Chabant <ludovic@chabant.com> |
---|---|
date | Thu, 24 Sep 2020 22:57:50 -0700 |
parents | ae0fb567f459 |
children | cfcac4ed7d21 |
rev | line source |
---|---|
0 | 1 import copy |
2 import logging | |
3 import os.path | |
4 import pickle | |
5 import re | |
6 import xml.etree.ElementTree as etree | |
7 | |
8 | |
9 # Known VS project types. | |
10 PROJ_TYPE_FOLDER = '2150E333-8FDC-42A3-9474-1A3956D46DE8' | |
11 PROJ_TYPE_NMAKE = '8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942' | |
12 PROJ_TYPE_CSHARP = 'FAE04EC0-301F-11D3-BF4B-00C04F79EFBC' | |
13 | |
14 PROJ_TYPE_NAMES = { | |
15 PROJ_TYPE_FOLDER: 'folder', | |
16 PROJ_TYPE_NMAKE: 'nmake', | |
17 PROJ_TYPE_CSHARP: 'csharp' | |
18 } | |
19 | |
20 # Known VS item types. | |
21 ITEM_TYPE_CPP_SRC = 'ClCompile' | |
22 ITEM_TYPE_CPP_HDR = 'ClInclude' | |
23 | |
24 ITEM_TYPE_CS_REF = 'Reference' | |
25 ITEM_TYPE_CS_PROJREF = 'ProjectReference' | |
26 ITEM_TYPE_CS_SRC = 'Compile' | |
27 | |
28 ITEM_TYPE_NONE = 'None' | |
29 | |
30 ITEM_TYPE_SOURCE_FILES = (ITEM_TYPE_CPP_SRC, ITEM_TYPE_CPP_HDR, | |
3
949c4f536f26
Add `None` file items to source solution files.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
31 ITEM_TYPE_CS_SRC, ITEM_TYPE_NONE) |
0 | 32 |
33 # Known VS properties. | |
34 PROP_CONFIGURATION_TYPE = 'ConfigurationType' | |
35 PROP_NMAKE_PREPROCESSOR_DEFINITIONS = 'NMakePreprocessorDefinitions' | |
36 PROP_NMAKE_INCLUDE_SEARCH_PATH = 'NMakeIncludeSearchPath' | |
37 | |
38 | |
39 logger = logging.getLogger(__name__) | |
40 | |
41 | |
42 def _strip_ns(tag): | |
43 """ Remove the XML namespace from a tag name. """ | |
44 if tag[0] == '{': | |
45 i = tag.index('}') | |
46 return tag[i+1:] | |
47 return tag | |
48 | |
49 | |
50 re_msbuild_var = re.compile(r'\$\((?P<var>[\w\d_]+)\)') | |
51 | |
52 | |
53 def _resolve_value(val, env): | |
54 """ Expands MSBuild property values given a build environment. """ | |
55 def _repl_vars(m): | |
56 varname = m.group('var') | |
57 varval = env.get(varname, '') | |
58 return varval | |
59 | |
60 if not val: | |
61 return val | |
62 return re_msbuild_var.sub(_repl_vars, val) | |
63 | |
64 | |
65 def _evaluate_condition(cond, env): | |
66 """ Expands MSBuild property values in a condition and evaluates it. """ | |
67 left, right = _resolve_value(cond, env).split('==') | |
68 return left == right | |
69 | |
70 | |
71 class VSBaseGroup: | |
72 """ Base class for VS project stuff that has conditional stuff inside. | |
73 | |
74 For instance, a property group called 'Blah' might have some common | |
75 (always valid) stuff, but a bunch of other stuff that should only | |
76 be considered when the solution configuration is Debug, Release, or | |
77 whatever else. In that case, each 'conditional' (i.e. values for Debug, | |
78 values for Release, etc.) is listed and tracked separately until | |
79 we are asked to 'resolve' ourselves based on a given build environment. | |
80 """ | |
81 def __init__(self, label): | |
82 self.label = label | |
83 self.conditionals = {} | |
84 | |
85 def get_conditional(self, condition): | |
86 """ Adds a conditional sub-group. """ | |
87 return self.conditionals.get(condition) | |
88 | |
89 def get_or_create_conditional(self, condition): | |
90 """ Gets or creates a new conditional sub-group. """ | |
91 c = self.get_conditional(condition) | |
92 if not c: | |
93 c = self.__class__(self.label) | |
94 self.conditionals[condition] = c | |
95 return c | |
96 | |
9
4ba6df1b2f97
Add built-in properties to the solution environment before resolving items.
Ludovic Chabant <ludovic@chabant.com>
parents:
4
diff
changeset
|
97 def _resolve(self, env): |
0 | 98 """ Resolves this group by evaluating each conditional sub-group |
99 based on the given build environment. Returns a 'flattened' | |
100 version of ourselves. | |
101 """ | |
102 c = self.__class__(self.label) | |
103 c._collapse_child(self, env) | |
104 | |
105 for cond, child in self.conditionals.items(): | |
106 if _evaluate_condition(cond, env): | |
107 c._collapse_child(child, env) | |
108 | |
109 return c | |
110 | |
111 | |
112 class VSProjectItem: | |
113 """ A VS project item, like a source code file. """ | |
114 def __init__(self, include, itemtype=None): | |
115 self.include = include | |
116 self.itemtype = itemtype | |
117 self.metadata = {} | |
118 | |
9
4ba6df1b2f97
Add built-in properties to the solution environment before resolving items.
Ludovic Chabant <ludovic@chabant.com>
parents:
4
diff
changeset
|
119 def _resolve(self, env): |
4ba6df1b2f97
Add built-in properties to the solution environment before resolving items.
Ludovic Chabant <ludovic@chabant.com>
parents:
4
diff
changeset
|
120 c = VSProjectItem(_resolve_value(self.include, env), self.itemtype) |
0 | 121 c.metadata = {k: _resolve_value(v, env) |
122 for k, v in self.metadata.items()} | |
123 return c | |
124 | |
125 def __str__(self): | |
126 return "(%s)%s" % (self.itemtype, self.include) | |
127 | |
128 | |
129 class VSProjectItemGroup(VSBaseGroup): | |
130 """ A VS project item group, like a list of source code files, | |
131 or a list of resources. | |
132 """ | |
133 def __init__(self, label): | |
134 super().__init__(label) | |
135 self.items = [] | |
136 | |
137 def get_source_items(self): | |
3
949c4f536f26
Add `None` file items to source solution files.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
138 return self.get_items_of_types(ITEM_TYPE_SOURCE_FILES) |
949c4f536f26
Add `None` file items to source solution files.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
139 |
9
4ba6df1b2f97
Add built-in properties to the solution environment before resolving items.
Ludovic Chabant <ludovic@chabant.com>
parents:
4
diff
changeset
|
140 def get_items_of_type(self, itemtype): |
4ba6df1b2f97
Add built-in properties to the solution environment before resolving items.
Ludovic Chabant <ludovic@chabant.com>
parents:
4
diff
changeset
|
141 for i in self.items: |
4ba6df1b2f97
Add built-in properties to the solution environment before resolving items.
Ludovic Chabant <ludovic@chabant.com>
parents:
4
diff
changeset
|
142 if i.itemtype == itemtype: |
4ba6df1b2f97
Add built-in properties to the solution environment before resolving items.
Ludovic Chabant <ludovic@chabant.com>
parents:
4
diff
changeset
|
143 yield i |
4ba6df1b2f97
Add built-in properties to the solution environment before resolving items.
Ludovic Chabant <ludovic@chabant.com>
parents:
4
diff
changeset
|
144 |
4ba6df1b2f97
Add built-in properties to the solution environment before resolving items.
Ludovic Chabant <ludovic@chabant.com>
parents:
4
diff
changeset
|
145 def get_items_of_types(self, itemtypes): |
4ba6df1b2f97
Add built-in properties to the solution environment before resolving items.
Ludovic Chabant <ludovic@chabant.com>
parents:
4
diff
changeset
|
146 typeset = set(itemtypes) |
0 | 147 for i in self.items: |
3
949c4f536f26
Add `None` file items to source solution files.
Ludovic Chabant <ludovic@chabant.com>
parents:
0
diff
changeset
|
148 if i.itemtype in typeset: |
0 | 149 yield i |
150 | |
151 def _collapse_child(self, child, env): | |
9
4ba6df1b2f97
Add built-in properties to the solution environment before resolving items.
Ludovic Chabant <ludovic@chabant.com>
parents:
4
diff
changeset
|
152 self.items += [i._resolve(env) for i in child.items] |
0 | 153 |
154 | |
155 class VSProjectProperty: | |
156 """ A VS project property, like an include path or compiler flag. """ | |
157 def __init__(self, name, value): | |
158 self.name = name | |
159 self.value = value | |
160 | |
9
4ba6df1b2f97
Add built-in properties to the solution environment before resolving items.
Ludovic Chabant <ludovic@chabant.com>
parents:
4
diff
changeset
|
161 def _resolve(self, env): |
0 | 162 c = VSProjectProperty(self.name, _resolve_value(self.value, env)) |
163 return c | |
164 | |
165 def __str__(self): | |
166 return "%s=%s" % (self.name, self.value) | |
167 | |
168 | |
169 class VSProjectPropertyGroup(VSBaseGroup): | |
170 """ A VS project property group, such as compiler macros or flags. """ | |
171 def __init__(self, label): | |
172 super().__init__(label) | |
173 self.properties = [] | |
174 | |
175 def get(self, propname): | |
176 try: | |
177 return self[propname] | |
178 except IndexError: | |
179 return None | |
180 | |
181 def __getitem__(self, propname): | |
182 for p in self.properties: | |
183 if p.name == propname: | |
184 return p.value | |
185 raise IndexError() | |
186 | |
187 def _collapse_child(self, child, env): | |
9
4ba6df1b2f97
Add built-in properties to the solution environment before resolving items.
Ludovic Chabant <ludovic@chabant.com>
parents:
4
diff
changeset
|
188 self.properties += [p._resolve(env) for p in child.properties] |
0 | 189 |
190 | |
191 class VSProject: | |
192 """ A VS project. """ | |
9
4ba6df1b2f97
Add built-in properties to the solution environment before resolving items.
Ludovic Chabant <ludovic@chabant.com>
parents:
4
diff
changeset
|
193 def __init__(self, owner, projtype, name, path, guid): |
4ba6df1b2f97
Add built-in properties to the solution environment before resolving items.
Ludovic Chabant <ludovic@chabant.com>
parents:
4
diff
changeset
|
194 self.owner = owner |
0 | 195 self.type = projtype |
196 self.name = name | |
197 self.path = path | |
198 self.guid = guid | |
199 self._itemgroups = None | |
200 self._propgroups = None | |
201 self._sln = None | |
202 | |
203 @property | |
204 def is_folder(self): | |
205 """ Returns whether this project is actually just a solution | |
206 folder, used as a container for other projects. | |
207 """ | |
208 return self.type == PROJ_TYPE_FOLDER | |
209 | |
210 @property | |
211 def abspath(self): | |
212 abspath = self.path | |
213 if self._sln and self._sln.path: | |
214 abspath = os.path.join(self._sln.dirpath, self.path) | |
215 return abspath | |
216 | |
217 @property | |
218 def absdirpath(self): | |
219 return os.path.dirname(self.abspath) | |
220 | |
221 @property | |
222 def itemgroups(self): | |
223 self._ensure_loaded() | |
224 return self._itemgroups.values() | |
225 | |
226 @property | |
227 def propertygroups(self): | |
228 self._ensure_loaded() | |
229 return self._propgroups.values() | |
230 | |
231 def itemgroup(self, label, resolved_with=None): | |
232 self._ensure_loaded() | |
233 ig = self._itemgroups.get(label) | |
234 if resolved_with is not None and ig is not None: | |
235 logger.debug("Resolving item group '%s'." % ig.label) | |
9
4ba6df1b2f97
Add built-in properties to the solution environment before resolving items.
Ludovic Chabant <ludovic@chabant.com>
parents:
4
diff
changeset
|
236 self._validate_build_env(resolved_with) |
4ba6df1b2f97
Add built-in properties to the solution environment before resolving items.
Ludovic Chabant <ludovic@chabant.com>
parents:
4
diff
changeset
|
237 ig = ig._resolve(resolved_with) |
0 | 238 return ig |
239 | |
240 def defaultitemgroup(self, resolved_with=None): | |
241 return self.itemgroup(None, resolved_with=resolved_with) | |
242 | |
243 def propertygroup(self, label, resolved_with=None): | |
244 self._ensure_loaded() | |
245 pg = self._propgroups.get(label) | |
246 if resolved_with is not None and pg is not None: | |
247 logger.debug("Resolving property group '%s'." % pg.label) | |
9
4ba6df1b2f97
Add built-in properties to the solution environment before resolving items.
Ludovic Chabant <ludovic@chabant.com>
parents:
4
diff
changeset
|
248 self._validate_build_env(resolved_with) |
4ba6df1b2f97
Add built-in properties to the solution environment before resolving items.
Ludovic Chabant <ludovic@chabant.com>
parents:
4
diff
changeset
|
249 pg = pg._resolve(resolved_with) |
0 | 250 return pg |
251 | |
252 def defaultpropertygroup(self, resolved_with=None): | |
253 return self.propertygroup(None, resolved_with=resolved_with) | |
254 | |
255 def get_abs_item_include(self, item): | |
256 return os.path.abspath(os.path.join(self.absdirpath, item.include)) | |
257 | |
258 def resolve(self, env): | |
259 self._ensure_loaded() | |
260 | |
9
4ba6df1b2f97
Add built-in properties to the solution environment before resolving items.
Ludovic Chabant <ludovic@chabant.com>
parents:
4
diff
changeset
|
261 self._validate_build_env(env) |
4ba6df1b2f97
Add built-in properties to the solution environment before resolving items.
Ludovic Chabant <ludovic@chabant.com>
parents:
4
diff
changeset
|
262 |
0 | 263 propgroups = list(self._propgroups) |
264 itemgroups = list(self._itemgroups) | |
265 self._propgroups[:] = [] | |
266 self._itemgroups[:] = [] | |
267 | |
268 for pg in propgroups: | |
9
4ba6df1b2f97
Add built-in properties to the solution environment before resolving items.
Ludovic Chabant <ludovic@chabant.com>
parents:
4
diff
changeset
|
269 rpg = pg._resolve(env) |
0 | 270 self._propgroups.append(rpg) |
271 | |
272 for ig in itemgroups: | |
9
4ba6df1b2f97
Add built-in properties to the solution environment before resolving items.
Ludovic Chabant <ludovic@chabant.com>
parents:
4
diff
changeset
|
273 rig = ig._resolve(env) |
0 | 274 self._itemgroups.append(rig) |
275 | |
9
4ba6df1b2f97
Add built-in properties to the solution environment before resolving items.
Ludovic Chabant <ludovic@chabant.com>
parents:
4
diff
changeset
|
276 def _validate_build_env(self, buildenv): |
4ba6df1b2f97
Add built-in properties to the solution environment before resolving items.
Ludovic Chabant <ludovic@chabant.com>
parents:
4
diff
changeset
|
277 buildenv['SolutionDir'] = self.owner.dirpath + os.path.sep |
4ba6df1b2f97
Add built-in properties to the solution environment before resolving items.
Ludovic Chabant <ludovic@chabant.com>
parents:
4
diff
changeset
|
278 buildenv['ProjectDir'] = self.absdirpath + os.path.sep |
4ba6df1b2f97
Add built-in properties to the solution environment before resolving items.
Ludovic Chabant <ludovic@chabant.com>
parents:
4
diff
changeset
|
279 |
0 | 280 def _ensure_loaded(self): |
281 if self._itemgroups is None or self._propgroups is None: | |
282 self._load() | |
283 | |
284 def _load(self): | |
285 if not self.path: | |
286 raise Exception("The current project has no path.") | |
287 if self.is_folder: | |
288 logger.debug(f"Skipping folder project {self.name}") | |
289 self._itemgroups = {} | |
290 self._propgroups = {} | |
291 return | |
292 | |
293 ns = {'ms': 'http://schemas.microsoft.com/developer/msbuild/2003'} | |
294 | |
295 abspath = self.abspath | |
296 logger.debug(f"Loading project {self.name} ({self.path}) from: {abspath}") | |
4
ae0fb567f459
Report error but don't crash when a solution points to a missing project.
Ludovic Chabant <ludovic@chabant.com>
parents:
3
diff
changeset
|
297 try: |
ae0fb567f459
Report error but don't crash when a solution points to a missing project.
Ludovic Chabant <ludovic@chabant.com>
parents:
3
diff
changeset
|
298 tree = etree.parse(abspath) |
ae0fb567f459
Report error but don't crash when a solution points to a missing project.
Ludovic Chabant <ludovic@chabant.com>
parents:
3
diff
changeset
|
299 except (FileNotFoundError, OSError) as ex: |
ae0fb567f459
Report error but don't crash when a solution points to a missing project.
Ludovic Chabant <ludovic@chabant.com>
parents:
3
diff
changeset
|
300 logger.debug(f"Error loading project {self.name}: " + str(ex)) |
ae0fb567f459
Report error but don't crash when a solution points to a missing project.
Ludovic Chabant <ludovic@chabant.com>
parents:
3
diff
changeset
|
301 self._itemgroups = {} |
ae0fb567f459
Report error but don't crash when a solution points to a missing project.
Ludovic Chabant <ludovic@chabant.com>
parents:
3
diff
changeset
|
302 self._propgroups= {} |
ae0fb567f459
Report error but don't crash when a solution points to a missing project.
Ludovic Chabant <ludovic@chabant.com>
parents:
3
diff
changeset
|
303 return |
ae0fb567f459
Report error but don't crash when a solution points to a missing project.
Ludovic Chabant <ludovic@chabant.com>
parents:
3
diff
changeset
|
304 |
0 | 305 root = tree.getroot() |
306 if _strip_ns(root.tag) != 'Project': | |
307 raise Exception(f"Expected root node 'Project', got '{root.tag}'") | |
308 | |
309 self._itemgroups = {} | |
310 for itemgroupnode in root.iterfind('ms:ItemGroup', ns): | |
311 label = itemgroupnode.attrib.get('Label') | |
312 itemgroup = self._itemgroups.get(label) | |
313 if not itemgroup: | |
314 itemgroup = VSProjectItemGroup(label) | |
315 self._itemgroups[label] = itemgroup | |
316 logger.debug(f"Adding itemgroup '{label}'") | |
317 | |
318 condition = itemgroupnode.attrib.get('Condition') | |
319 if condition: | |
320 itemgroup = itemgroup.get_or_create_conditional(condition) | |
321 | |
322 for itemnode in itemgroupnode: | |
323 incval = itemnode.attrib.get('Include') | |
324 item = VSProjectItem(incval, _strip_ns(itemnode.tag)) | |
325 itemgroup.items.append(item) | |
326 for metanode in itemnode: | |
327 item.metadata[_strip_ns(metanode.tag)] = metanode.text | |
328 | |
329 self._propgroups = {} | |
330 for propgroupnode in root.iterfind('ms:PropertyGroup', ns): | |
331 label = propgroupnode.attrib.get('Label') | |
332 propgroup = self._propgroups.get(label) | |
333 if not propgroup: | |
334 propgroup = VSProjectPropertyGroup(label) | |
335 self._propgroups[label] = propgroup | |
336 logger.debug(f"Adding propertygroup '{label}'") | |
337 | |
338 condition = propgroupnode.attrib.get('Condition') | |
339 if condition: | |
340 propgroup = propgroup.get_or_create_conditional(condition) | |
341 | |
342 for propnode in propgroupnode: | |
343 propgroup.properties.append(VSProjectProperty( | |
344 _strip_ns(propnode.tag), | |
345 propnode.text)) | |
346 | |
347 | |
348 class MissingVSProjectError(Exception): | |
349 pass | |
350 | |
351 | |
352 class VSGlobalSectionEntry: | |
353 """ An entry in a VS solution's global section. """ | |
354 def __init__(self, name, value): | |
355 self.name = name | |
356 self.value = value | |
357 | |
358 | |
359 class VSGlobalSection: | |
360 """ A global section in a VS solution. """ | |
361 def __init__(self, name): | |
362 self.name = name | |
363 self.entries = [] | |
364 | |
365 | |
366 class VSSolution: | |
367 """ A VS solution. """ | |
368 def __init__(self, path=None): | |
369 self.path = path | |
370 self.projects = [] | |
371 self.sections = [] | |
372 | |
373 @property | |
374 def dirpath(self): | |
375 return os.path.dirname(self.path) | |
376 | |
377 def find_project_by_name(self, name, missing_ok=True): | |
378 for p in self.projects: | |
379 if p.name == name: | |
380 return p | |
381 if missing_ok: | |
382 return None | |
383 return MissingVSProjectError(f"Can't find project with name: {name}") | |
384 | |
385 def find_project_by_path(self, path, missing_ok=True): | |
386 for p in self.projects: | |
387 if p.abspath == path: | |
388 return p | |
389 if missing_ok: | |
390 return None | |
391 raise MissingVSProjectError(f"Can't find project with path: {path}") | |
392 | |
393 def find_project_by_guid(self, guid, missing_ok=True): | |
394 for p in self.projects: | |
395 if p.guid == guid: | |
396 return p | |
397 if missing_ok: | |
398 return None | |
399 raise MissingVSProjectError(f"Can't find project for guid: {guid}") | |
400 | |
401 def globalsection(self, name): | |
402 for sec in self.sections: | |
403 if sec.name == name: | |
404 return sec | |
405 return None | |
406 | |
407 def find_project_configuration(self, proj_guid, sln_config): | |
408 configs = self.globalsection('ProjectConfigurationPlatforms') | |
409 if not configs: | |
410 return None | |
411 | |
412 entry_name = '{%s}.%s.Build.0' % (proj_guid, sln_config) | |
413 for entry in configs.entries: | |
414 if entry.name == entry_name: | |
415 return entry.value | |
416 return None | |
417 | |
418 | |
419 _re_sln_project_decl_start = re.compile( | |
420 r'^Project\("\{(?P<type>[A-Z0-9\-]+)\}"\) \= ' | |
421 r'"(?P<name>[^"]+)", "(?P<path>[^"]+)", "\{(?P<guid>[A-Z0-9\-]+)\}"$') | |
422 _re_sln_project_decl_end = re.compile( | |
423 r'^EndProject$') | |
424 | |
425 _re_sln_global_start = re.compile(r'^Global$') | |
426 _re_sln_global_end = re.compile(r'^EndGlobal$') | |
427 _re_sln_global_section_start = re.compile( | |
428 r'^\s*GlobalSection\((?P<name>\w+)\) \= (?P<step>\w+)$') | |
429 _re_sln_global_section_end = re.compile(r'^\s*EndGlobalSection$') | |
430 | |
431 | |
432 def parse_sln_file(slnpath): | |
433 """ Parses a solution file, returns a solution object. | |
434 The projects are not loaded (they will be lazily loaded upon | |
435 first access to their items/properties/etc.). | |
436 """ | |
437 logging.debug(f"Reading {slnpath}") | |
438 slnobj = VSSolution(slnpath) | |
439 with open(slnpath, 'r') as fp: | |
440 lines = fp.readlines() | |
441 _parse_sln_file_text(slnobj, lines) | |
442 return slnobj | |
443 | |
444 | |
445 def _parse_sln_file_text(slnobj, lines): | |
446 until = None | |
447 in_global = False | |
448 in_global_section = None | |
449 | |
450 for i, line in enumerate(lines): | |
451 if until: | |
452 # We need to parse something until a given token, so let's | |
453 # do that and ignore everything else. | |
454 m = until.search(line) | |
455 if m: | |
456 until = None | |
457 continue | |
458 | |
459 if in_global: | |
460 # We're in the 'global' part of the solution. It should contain | |
461 # a bunch of 'global sections' that we need to parse individually. | |
462 if in_global_section: | |
463 # Keep parsing the current section until we reach the end. | |
464 m = _re_sln_global_section_end.search(line) | |
465 if m: | |
466 in_global_section = None | |
467 continue | |
468 | |
469 ename, evalue = line.strip().split('=') | |
470 in_global_section.entries.append(VSGlobalSectionEntry( | |
471 ename.strip(), | |
472 evalue.strip())) | |
473 continue | |
474 | |
475 m = _re_sln_global_section_start.search(line) | |
476 if m: | |
477 # Found the start of a new section. | |
478 in_global_section = VSGlobalSection(m.group('name')) | |
479 logging.debug(f" Adding global section {in_global_section.name}") | |
480 slnobj.sections.append(in_global_section) | |
481 continue | |
482 | |
483 m = _re_sln_global_end.search(line) | |
484 if m: | |
485 # Found the end of the 'global' part. | |
486 in_global = False | |
487 continue | |
488 | |
489 # We're not in a specific part of the solution, so do high-level | |
490 # parsing. First, ignore root-level comments. | |
491 if not line or line[0] == '#': | |
492 continue | |
493 | |
494 m = _re_sln_project_decl_start.search(line) | |
495 if m: | |
496 # Found the start of a project declaration. | |
497 try: | |
498 p = VSProject( | |
9
4ba6df1b2f97
Add built-in properties to the solution environment before resolving items.
Ludovic Chabant <ludovic@chabant.com>
parents:
4
diff
changeset
|
499 slnobj, |
0 | 500 m.group('type'), m.group('name'), m.group('path'), |
501 m.group('guid')) | |
502 except: | |
503 raise Exception(f"Error line {i}: unexpected project syntax.") | |
504 logging.debug(f" Adding project {p.name}") | |
505 slnobj.projects.append(p) | |
506 p._sln = slnobj | |
507 | |
508 until = _re_sln_project_decl_end | |
509 continue | |
510 | |
511 m = _re_sln_global_start.search(line) | |
512 if m: | |
513 # Reached the start of the 'global' part, where global sections | |
514 # are defined. | |
515 in_global = True | |
516 continue | |
517 | |
518 # Ignore the rest (like visual studio version flags). | |
519 continue | |
520 | |
521 | |
522 class SolutionCache: | |
523 """ A class that contains a VS solution object, along with pre-indexed | |
524 lists of items. It's meant to be saved on disk. | |
525 """ | |
9
4ba6df1b2f97
Add built-in properties to the solution environment before resolving items.
Ludovic Chabant <ludovic@chabant.com>
parents:
4
diff
changeset
|
526 VERSION = 4 |
0 | 527 |
528 def __init__(self, slnobj): | |
529 self.slnobj = slnobj | |
530 self.index = None | |
9
4ba6df1b2f97
Add built-in properties to the solution environment before resolving items.
Ludovic Chabant <ludovic@chabant.com>
parents:
4
diff
changeset
|
531 self._saved_version = SolutionCache.VERSION |
0 | 532 |
533 def build_cache(self): | |
534 self.index = {} | |
535 for proj in self.slnobj.projects: | |
536 if proj.is_folder: | |
537 continue | |
538 itemgroup = proj.defaultitemgroup() | |
539 if not itemgroup: | |
540 continue | |
541 | |
542 item_cache = set() | |
543 self.index[proj.abspath] = item_cache | |
544 | |
545 for item in itemgroup.get_source_items(): | |
546 item_path = proj.get_abs_item_include(item).lower() | |
547 item_cache.add(item_path) | |
548 | |
549 def save(self, path): | |
550 pathdir = os.path.dirname(path) | |
551 if not os.path.exists(pathdir): | |
552 os.makedirs(pathdir) | |
553 with open(path, 'wb') as fp: | |
554 pickle.dump(self, fp) | |
555 | |
556 @staticmethod | |
557 def load_or_rebuild(slnpath, cachepath): | |
558 if cachepath: | |
559 res = _try_load_from_cache(slnpath, cachepath) | |
560 if res is not None: | |
561 return res | |
562 | |
563 slnobj = parse_sln_file(slnpath) | |
564 cache = SolutionCache(slnobj) | |
565 | |
566 if cachepath: | |
567 logger.debug(f"Regenerating cache: {cachepath}") | |
568 cache.build_cache() | |
569 cache.save(cachepath) | |
570 | |
571 return (cache, False) | |
572 | |
573 | |
574 def _try_load_from_cache(slnpath, cachepath): | |
575 try: | |
576 sln_dt = os.path.getmtime(slnpath) | |
577 cache_dt = os.path.getmtime(cachepath) | |
578 except OSError: | |
579 logger.debug("Can't read solution or cache files.") | |
580 return None | |
581 | |
582 # If the solution file is newer, bail out. | |
583 if sln_dt >= cache_dt: | |
584 logger.debug("Solution is newer than cache.") | |
585 return None | |
586 | |
587 # Our cache is at least valid for the solution stuff. Some of our | |
588 # projects might be out of date, but at least there can't be any | |
589 # added or removed projects from the solution (otherwise the solution | |
590 # file would have been touched). Let's load the cache. | |
591 with open(cachepath, 'rb') as fp: | |
592 cache = pickle.load(fp) | |
593 | |
594 # Check that the cache version is up-to-date with this code. | |
9
4ba6df1b2f97
Add built-in properties to the solution environment before resolving items.
Ludovic Chabant <ludovic@chabant.com>
parents:
4
diff
changeset
|
595 loaded_ver = getattr(cache, '_saved_version', 0) |
0 | 596 if loaded_ver != SolutionCache.VERSION: |
9
4ba6df1b2f97
Add built-in properties to the solution environment before resolving items.
Ludovic Chabant <ludovic@chabant.com>
parents:
4
diff
changeset
|
597 logger.debug(f"Cache was saved with older format: {cachepath} " |
4ba6df1b2f97
Add built-in properties to the solution environment before resolving items.
Ludovic Chabant <ludovic@chabant.com>
parents:
4
diff
changeset
|
598 f"(got {loaded_ver}, expected {SolutionCache.VERSION})") |
0 | 599 return None |
9
4ba6df1b2f97
Add built-in properties to the solution environment before resolving items.
Ludovic Chabant <ludovic@chabant.com>
parents:
4
diff
changeset
|
600 logger.debug(f"Cache has correct version: {loaded_ver}") |
0 | 601 |
602 slnobj = cache.slnobj | |
603 | |
604 # Check that none of the project files in the solution are newer | |
605 # than this cache. | |
606 proj_dts = [] | |
607 for p in slnobj.projects: | |
608 if not p.is_folder: | |
609 try: | |
610 proj_dts.append(os.path.getmtime(p.abspath)) | |
611 except OSError: | |
612 logger.debug(f"Found missing project: {p.abspath}") | |
613 return None | |
614 | |
615 if all([cache_dt > pdt for pdt in proj_dts]): | |
616 logger.debug(f"Cache is up to date: {cachepath}") | |
617 return (cache, True) | |
618 | |
619 logger.debug("Cache has outdated projects.") | |
620 return None |