diff --git a/vcxproj2cmake/MSBuildProject.cs b/vcxproj2cmake/MSBuildProject.cs index 4a69d28..b0e66d4 100644 --- a/vcxproj2cmake/MSBuildProject.cs +++ b/vcxproj2cmake/MSBuildProject.cs @@ -1,7 +1,8 @@ using Microsoft.Extensions.Logging; +using Microsoft.Build.Construction; using System.IO.Abstractions; using System.Text.RegularExpressions; -using System.Xml.Linq; +using System.Xml; namespace vcxproj2cmake; @@ -47,94 +48,66 @@ public static MSBuildProject ParseProjectFile(string projectPath, IFileSystem fi { logger.LogInformation($"Parsing {projectPath}"); - var msbuildNamespace = "http://schemas.microsoft.com/developer/msbuild/2003"; - var clCompileXName = XName.Get("ClCompile", msbuildNamespace); - var clIncludeXName = XName.Get("ClInclude", msbuildNamespace); - var importGroupXName = XName.Get("ImportGroup", msbuildNamespace); - var importXName = XName.Get("Import", msbuildNamespace); - var itemDefinitionGroupXName = XName.Get("ItemDefinitionGroup", msbuildNamespace); - var itemGroupXName = XName.Get("ItemGroup", msbuildNamespace); - var libXName = XName.Get("Lib", msbuildNamespace); - var linkLibraryDependenciesXName = XName.Get("LinkLibraryDependencies", msbuildNamespace); - var linkXName = XName.Get("Link", msbuildNamespace); - var projectConfigurationXName = XName.Get("ProjectConfiguration", msbuildNamespace); - var projectReferenceXName = XName.Get("ProjectReference", msbuildNamespace); - var projectXName = XName.Get("Project", msbuildNamespace); - var propertyGroupXName = XName.Get("PropertyGroup", msbuildNamespace); - var qtMocXName = XName.Get("QtMoc", msbuildNamespace); - var qtModulesXName = XName.Get("QtModules", msbuildNamespace); - var qtRccXName = XName.Get("QtRcc", msbuildNamespace); - var qtUicXName = XName.Get("QtUic", msbuildNamespace); - - XDocument doc; projectPath = PathUtils.NormalizePathSeparators(projectPath); - using (var fileStream = fileSystem.FileStream.New(projectPath, FileMode.Open, FileAccess.Read, FileShare.Read)) - doc = XDocument.Load(fileStream); + ProjectRootElement projectElement; - var projectElement = doc.Element(projectXName)!; + using (var stream = fileSystem.FileStream.New(projectPath, FileMode.Open, FileAccess.Read, FileShare.Read)) + using (var reader = XmlReader.Create(stream)) + projectElement = ProjectRootElement.Create(reader); + + projectElement.FullPath = projectPath; var projectConfigurations = - projectElement - .Elements(itemGroupXName) - .SelectMany(group => group.Elements(projectConfigurationXName)) - .Select(element => PathUtils.NormalizePathSeparators(element.Attribute("Include")!.Value.Trim())) + projectElement.ItemGroups + .SelectMany(g => g.Items.Where(i => i.ItemType == "ProjectConfiguration")) + .Select(i => PathUtils.NormalizePathSeparators(i.Include.Trim())) .Select(config => new MSBuildProjectConfig(config)) .ToList(); var sourceFiles = - projectElement - .Elements(itemGroupXName) - .SelectMany(group => - group.Elements(clCompileXName) - .Concat(group.Elements(qtUicXName)) - .Concat(group.Elements(qtRccXName))) - .Select(element => PathUtils.NormalizePathSeparators(element.Attribute("Include")!.Value.Trim())) + projectElement.ItemGroups + .SelectMany(g => g.Items.Where(i => i.ItemType == "ClCompile" || i.ItemType == "QtUic" || i.ItemType == "QtRcc")) + .Select(i => PathUtils.NormalizePathSeparators(i.Include.Trim())) .ToList(); var headerFiles = - projectElement - .Elements(itemGroupXName) - .SelectMany(group => group.Elements(clIncludeXName)) - .Select(element => PathUtils.NormalizePathSeparators(element.Attribute("Include")!.Value.Trim())) + projectElement.ItemGroups + .SelectMany(g => g.Items.Where(i => i.ItemType == "ClInclude")) + .Select(i => PathUtils.NormalizePathSeparators(i.Include.Trim())) .ToList(); var qtModules = - projectElement - .Elements(propertyGroupXName) - .SelectMany(group => group.Elements(qtModulesXName)) - .Select(element => element.Value.Trim()) + projectElement.PropertyGroups + .SelectMany(g => g.Properties.Where(p => p.Name == "QtModules")) + .Select(p => p.Value.Trim()) .Distinct() .SingleOrDefaultWithException(string.Empty, () => throw new CatastrophicFailureException("Qt modules are inconsistent between configurations")) .Split(';', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries); var imports = - projectElement - .Elements(importXName) - .Concat(projectElement.Elements(importGroupXName).SelectMany(group => group.Elements(importXName))) - .Select(import => PathUtils.NormalizePathSeparators(import.Attribute("Project")!.Value.Trim())) + projectElement.Imports.Select(i => PathUtils.NormalizePathSeparators(i.Project.Trim())) + .Concat(projectElement.ImportGroups.SelectMany(g => g.Imports.Select(i => PathUtils.NormalizePathSeparators(i.Project.Trim())))) .ToList(); var projectReferences = - projectElement - .Elements(itemGroupXName) - .SelectMany(group => group.Elements(projectReferenceXName)) - .Select(element => PathUtils.NormalizePathSeparators(element.Attribute("Include")!.Value.Trim())) + projectElement.ItemGroups + .SelectMany(g => g.Items.Where(i => i.ItemType == "ProjectReference")) + .Select(i => PathUtils.NormalizePathSeparators(i.Include.Trim())) .Distinct() .ToList(); var linkLibraryDependenciesEnabled = - projectElement - .Elements(itemDefinitionGroupXName) - .SelectMany(group => group.Elements(projectReferenceXName)) - .SelectMany(element => element.Elements(linkLibraryDependenciesXName)) - .Select(element => element.Value.Trim() switch + projectElement.ItemDefinitionGroups + .SelectMany(g => g.ItemDefinitions.Where(d => d.ItemType == "ProjectReference")) + .SelectMany(d => d.Metadata.Where(m => m.Name == "LinkLibraryDependencies")) + .Select(m => m.Value.Trim() switch { "true" => true, "false" => false, _ => throw new CatastrophicFailureException( - $"Invalid value for LinkLibraryDependencies: {element.Value}") + $"Invalid value for LinkLibraryDependencies: {m.Value}") }) .Distinct() .SingleOrDefaultWithException(true, @@ -142,22 +115,19 @@ public static MSBuildProject ParseProjectFile(string projectPath, IFileSystem fi "LinkLibraryDependencies property is inconsistent between configurations")); var requiresQtMoc = - projectElement - .Elements(itemGroupXName) - .SelectMany(group => group.Elements(qtMocXName)) - .Any(); + projectElement.ItemGroups + .SelectMany(g => g.Items) + .Any(i => i.ItemType == "QtMoc"); var requiresQtUic = - projectElement - .Elements(itemGroupXName) - .SelectMany(group => group.Elements(qtUicXName)) - .Any(); + projectElement.ItemGroups + .SelectMany(g => g.Items) + .Any(i => i.ItemType == "QtUic"); var requiresQtRcc = - projectElement - .Elements(itemGroupXName) - .SelectMany(group => group.Elements(qtRccXName)) - .Any(); + projectElement.ItemGroups + .SelectMany(g => g.Items) + .Any(i => i.ItemType == "QtRcc"); Dictionary> compilerSettings = []; Dictionary> linkerSettings = []; @@ -166,38 +136,35 @@ public static MSBuildProject ParseProjectFile(string projectPath, IFileSystem fi foreach (var projectConfig in projectConfigurations) { var itemDefinitionGroups = - projectElement - .Elements(itemDefinitionGroupXName) - .Where(group => group.Attribute("Condition") == null || - Regex.IsMatch(group.Attribute("Condition")!.Value, + projectElement.ItemDefinitionGroups + .Where(group => string.IsNullOrEmpty(group.Condition) || + Regex.IsMatch(group.Condition!, $@"'\$\(Configuration\)\|\$\(Platform\)'\s*==\s*'{Regex.Escape(projectConfig.Name)}'")) .ToList(); var propertyGroups = - projectElement - .Elements(propertyGroupXName) - .Where(group => group.Attribute("Condition") == null || - Regex.IsMatch(group.Attribute("Condition")!.Value, + projectElement.PropertyGroups + .Where(group => string.IsNullOrEmpty(group.Condition) || + Regex.IsMatch(group.Condition!, $@"'\$\(Configuration\)\|\$\(Platform\)'\s*==\s*'{Regex.Escape(projectConfig.Name)}'")) .ToList(); var projectConfigCompilerSettings = itemDefinitionGroups - .SelectMany(group => group.Elements(clCompileXName)) - .SelectMany(element => element.Elements()) - .ToDictionaryKeepingLast(element => element.Name.LocalName, element => element.Value.Trim()); + .SelectMany(group => group.ItemDefinitions.Where(d => d.ItemType == "ClCompile")) + .SelectMany(element => element.Metadata) + .ToDictionaryKeepingLast(element => element.Name, element => element.Value.Trim()); var projectConfigLinkerSettings = itemDefinitionGroups - .SelectMany(group => group.Elements()) - .Where(element => element.Name == linkXName || element.Name == libXName) - .SelectMany(element => element.Elements()) - .ToDictionaryKeepingLast(element => element.Name.LocalName, element => element.Value.Trim()); + .SelectMany(group => group.ItemDefinitions.Where(d => d.ItemType == "Link" || d.ItemType == "Lib")) + .SelectMany(element => element.Metadata) + .ToDictionaryKeepingLast(element => element.Name, element => element.Value.Trim()); var projectConfigOtherSettings = propertyGroups - .SelectMany(element => element.Elements()) - .ToDictionaryKeepingLast(element => element.Name.LocalName, element => element.Value.Trim()); + .SelectMany(group => group.Properties) + .ToDictionaryKeepingLast(prop => prop.Name, prop => prop.Value.Trim()); foreach (var setting in projectConfigCompilerSettings) { diff --git a/vcxproj2cmake/vcxproj2cmake.csproj b/vcxproj2cmake/vcxproj2cmake.csproj index 11850a6..0b43dc1 100644 --- a/vcxproj2cmake/vcxproj2cmake.csproj +++ b/vcxproj2cmake/vcxproj2cmake.csproj @@ -38,6 +38,7 @@ +