Mercurial > vim-crosoft
changeset 15:cfcac4ed7d21 default tip
Improve loading of solution files
- New argument to force a rebuild of the cache
- Gracefully handle missing projects in a solution
- Handle more different xml namespaces
- Support more edge cases
author | Ludovic Chabant <ludovic@chabant.com> |
---|---|
date | Tue, 29 Aug 2023 12:59:54 -0700 |
parents | 0aa61944e518 |
children | |
files | scripts/list_sln_files.py scripts/vsutil.py |
diffstat | 2 files changed, 83 insertions(+), 45 deletions(-) [+] |
line wrap: on
line diff
--- a/scripts/list_sln_files.py Tue Aug 29 12:52:12 2023 -0700 +++ b/scripts/list_sln_files.py Tue Aug 29 12:59:54 2023 -0700 @@ -14,6 +14,9 @@ help="The path to the Visual Studio solution file.") parser.add_argument('-c', '--cache', help="The solution cache file to load.") + parser.add_argument('--rebuild-cache', + action='store_true', + help="Force rebuild the cache even if it is valid") parser.add_argument('--list-cache', help=("If the solution cache is valid, use this " "pre-saved file list. Otherwise, compute the " @@ -29,7 +32,8 @@ args = parser.parse_args(args) setup_logging(args.verbose) - cache, loaded = SolutionCache.load_or_rebuild(args.solution, args.cache) + cache, loaded = SolutionCache.load_or_rebuild(args.solution, args.cache, + args.rebuild_cache) if loaded and args.list_cache: caches_exist = True try: @@ -61,10 +65,13 @@ for p in projs: ig = p.defaultitemgroup() + if ig is None: + continue for i in ig.get_items_of_types(itemtypes): - file_path = os.path.abspath(os.path.join(p.absdirpath, i.include)) - print(file_path) - items.append(file_path + '\n') + if i.include: + file_path = os.path.abspath(os.path.join(p.absdirpath, i.include)) + print(file_path) + items.append(file_path + '\n') if args.list_cache: logger.debug("Writing file list cache: %s" % args.list_cache)
--- a/scripts/vsutil.py Tue Aug 29 12:52:12 2023 -0700 +++ b/scripts/vsutil.py Tue Aug 29 12:59:54 2023 -0700 @@ -199,6 +199,7 @@ self._itemgroups = None self._propgroups = None self._sln = None + self._missing = False @property def is_folder(self): @@ -300,49 +301,62 @@ logger.debug(f"Error loading project {self.name}: " + str(ex)) self._itemgroups = {} self._propgroups= {} + self._missing = True return root = tree.getroot() if _strip_ns(root.tag) != 'Project': raise Exception(f"Expected root node 'Project', got '{root.tag}'") + # Load ItemGroups and PropertyGroups via both namespaced names and raw + # names because not all types of VS projects use the MS namespaces. self._itemgroups = {} + for itemgroupnode in root.iterfind('ItemGroup', ns): + self._load_item_group(itemgroupnode) for itemgroupnode in root.iterfind('ms:ItemGroup', ns): - label = itemgroupnode.attrib.get('Label') - itemgroup = self._itemgroups.get(label) - if not itemgroup: - itemgroup = VSProjectItemGroup(label) - self._itemgroups[label] = itemgroup - logger.debug(f"Adding itemgroup '{label}'") - - condition = itemgroupnode.attrib.get('Condition') - if condition: - itemgroup = itemgroup.get_or_create_conditional(condition) - - for itemnode in itemgroupnode: - incval = itemnode.attrib.get('Include') - item = VSProjectItem(incval, _strip_ns(itemnode.tag)) - itemgroup.items.append(item) - for metanode in itemnode: - item.metadata[_strip_ns(metanode.tag)] = metanode.text + self._load_item_group(itemgroupnode) self._propgroups = {} + for propgroupnode in root.iterfind('PropertyGroup', ns): + self._load_property_group(propgroupnode) for propgroupnode in root.iterfind('ms:PropertyGroup', ns): - label = propgroupnode.attrib.get('Label') - propgroup = self._propgroups.get(label) - if not propgroup: - propgroup = VSProjectPropertyGroup(label) - self._propgroups[label] = propgroup - logger.debug(f"Adding propertygroup '{label}'") + self._load_property_group(propgroupnode) + + def _load_item_group(self, itemgroupnode): + label = itemgroupnode.attrib.get('Label') + itemgroup = self._itemgroups.get(label) + if not itemgroup: + itemgroup = VSProjectItemGroup(label) + self._itemgroups[label] = itemgroup + logger.debug(f"Adding itemgroup '{label}'") + + condition = itemgroupnode.attrib.get('Condition') + if condition: + itemgroup = itemgroup.get_or_create_conditional(condition) - condition = propgroupnode.attrib.get('Condition') - if condition: - propgroup = propgroup.get_or_create_conditional(condition) + for itemnode in itemgroupnode: + incval = itemnode.attrib.get('Include') + item = VSProjectItem(incval, _strip_ns(itemnode.tag)) + itemgroup.items.append(item) + for metanode in itemnode: + item.metadata[_strip_ns(metanode.tag)] = metanode.text - for propnode in propgroupnode: - propgroup.properties.append(VSProjectProperty( - _strip_ns(propnode.tag), - propnode.text)) + def _load_property_group(self, propgroupnode): + label = propgroupnode.attrib.get('Label') + propgroup = self._propgroups.get(label) + if not propgroup: + propgroup = VSProjectPropertyGroup(label) + self._propgroups[label] = propgroup + logger.debug(f"Adding propertygroup '{label}'") + + condition = propgroupnode.attrib.get('Condition') + if condition: + propgroup = propgroup.get_or_create_conditional(condition) + + for propnode in propgroupnode: + propgroup.properties.append(VSProjectProperty( + _strip_ns(propnode.tag), + propnode.text)) class MissingVSProjectError(Exception): @@ -476,7 +490,7 @@ if m: # Found the start of a new section. in_global_section = VSGlobalSection(m.group('name')) - logging.debug(f" Adding global section {in_global_section.name}") + logging.debug(f" Adding global section {in_global_section.name} (line {i})") slnobj.sections.append(in_global_section) continue @@ -501,7 +515,7 @@ m.group('guid')) except: raise Exception(f"Error line {i}: unexpected project syntax.") - logging.debug(f" Adding project {p.name}") + logging.debug(f" Adding project {p.name} (line {i})") slnobj.projects.append(p) p._sln = slnobj @@ -523,7 +537,7 @@ """ A class that contains a VS solution object, along with pre-indexed lists of items. It's meant to be saved on disk. """ - VERSION = 4 + VERSION = 5 def __init__(self, slnobj): self.slnobj = slnobj @@ -543,8 +557,12 @@ self.index[proj.abspath] = item_cache for item in itemgroup.get_source_items(): - item_path = proj.get_abs_item_include(item).lower() - item_cache.add(item_path) + if item.include: + item_path = proj.get_abs_item_include(item).lower() + item_cache.add(item_path) + # else: it's an item from our shortlist (cpp, cs, etc files) + # but it somehow doesn't have a path, which can happen with + # some obscure VS features. def save(self, path): pathdir = os.path.dirname(path) @@ -554,8 +572,8 @@ pickle.dump(self, fp) @staticmethod - def load_or_rebuild(slnpath, cachepath): - if cachepath: + def load_or_rebuild(slnpath, cachepath, force_rebuild=False): + if cachepath and not force_rebuild: res = _try_load_from_cache(slnpath, cachepath) if res is not None: return res @@ -588,8 +606,14 @@ # projects might be out of date, but at least there can't be any # added or removed projects from the solution (otherwise the solution # file would have been touched). Let's load the cache. - with open(cachepath, 'rb') as fp: - cache = pickle.load(fp) + try: + with open(cachepath, 'rb') as fp: + cache = pickle.load(fp) + except Exception as ex: + logger.debug("Error loading solution cache: %s" % ex) + logger.debug("Deleting cache: %s" % cachepath) + os.remove(cachepath) + return None # Check that the cache version is up-to-date with this code. loaded_ver = getattr(cache, '_saved_version', 0) @@ -608,9 +632,16 @@ if not p.is_folder: try: proj_dts.append(os.path.getmtime(p.abspath)) + # The project was missing last time we built the cache, + # but now it exists. Force a rebuild. + if p._missing: + return None except OSError: - logger.debug(f"Found missing project: {p.abspath}") - return None + if not p._missing: + logger.debug(f"Found missing project: {p.abspath}") + return None + # else: it was already missing last time we built the + # cache, so nothing has changed. if all([cache_dt > pdt for pdt in proj_dts]): logger.debug(f"Cache is up to date: {cachepath}")