/// <summary> /// Creates a new <see cref="SolutionBuilder"/> from an MSBuild traversal project. /// </summary> /// <param name="projectPath"> /// The path to the traversal project from which to populate the returned <see cref="SolutionBuilder"/>. /// </param> /// <param name="solutionOutputPath"> /// The path to the solution that the returned <see cref="SolutionBuilder"/> should /// represent. This path is used to compute the relative path to projects. /// </param> /// <param name="addTransitiveProjectReferences"> /// Whether or not to add transitive `<ProjectReference>` projects to the solution. /// </param> /// <param name="log"> /// An optional logger. /// </param> public static SolutionBuilder FromTraversalProject( string projectPath, string solutionOutputPath = null, bool addTransitiveProjectReferences = true, TaskLoggingHelper log = null) { if (projectPath == null) { throw new ArgumentNullException(nameof(projectPath)); } if (!File.Exists(projectPath)) { throw new FileNotFoundException($"project does not exist: {projectPath}"); } projectPath = ResolveFullPath(projectPath); if (string.IsNullOrEmpty(solutionOutputPath)) { solutionOutputPath = Path.ChangeExtension(projectPath, ".sln"); } log?.LogMessage(MessageImportance.High, "Generating solution from traversal project"); log?.LogMessage(MessageImportance.Normal, $" projectPath: {projectPath}"); log?.LogMessage(MessageImportance.Normal, $" solutionOutputPath: {solutionOutputPath}"); log?.LogMessage(MessageImportance.Normal, $" addTransitiveProjectReferences: {addTransitiveProjectReferences}"); var solution = new SolutionBuilder(solutionOutputPath, log); var traversalProject = new ProjectCollection().LoadProject(projectPath); // Add the explicit solution configurations foreach (var item in traversalProject.GetItems("SolutionConfiguration")) { solution.AddSolutionConfiguration(ConfigurationPlatform.Parse(item.EvaluatedInclude)); } // For each solution configuration, re-evaluate the whole project collection // with the mapped solution -> project configurations. This can result in // different project references (e.g. cross platform projects that might build // on windows and not mac, etc.). foreach (var item in traversalProject.GetItems("SolutionConfiguration")) { var solutionConfigurationPlatform = ConfigurationPlatform.Parse(item.EvaluatedInclude); var projectConfigurationPlatform = new ConfigurationPlatform( item.GetMetadataValue("Configuration") ?? solutionConfigurationPlatform.Configuration, item.GetMetadataValue("Platform") ?? solutionConfigurationPlatform.Platform); var globalProperties = new List <(string, string)> { ("IsGeneratingSolution", "true"), ("Configuration", projectConfigurationPlatform.Configuration), ("Platform", projectConfigurationPlatform.Platform) }; globalProperties.AddRange(item.Metadata.Select(m => (m.Name, m.EvaluatedValue))); var graph = DependencyGraph .Create(projectPath, globalProperties) .LoadGraph(); // Add each project. Projects may fail to load in MSBuild, for example, if an SDK // is not available via the running MSBuild toolchain. If this is the case, we // still need to handle adding the project to the solution, we just can't infer // anything therein. foreach (var node in graph.TopologicallySortedProjects) { // Don't add the root traversal project to the solution if (node.Parents.Count == 0) { continue; } // Prefer an explicit project GUID if one exists (old style projects) Guid projectGuid = default; var explicitProjectGuid = node.Project == null ? XDocument .Load(node.ProjectPath) .Root .Elements() .FirstOrDefault(e => !e.HasAttributes && string.Equals( e.Name.LocalName, "PropertyGroup", StringComparison.OrdinalIgnoreCase)) ?.Elements() .FirstOrDefault(e => string.Equals( e.Name.LocalName, "ProjectGuid", StringComparison.OrdinalIgnoreCase)) ?.Value : node.Project.GetPropertyValue("ProjectGuid"); if (!string.IsNullOrEmpty(explicitProjectGuid)) { Guid.TryParse(explicitProjectGuid, out projectGuid); } string solutionFolder = null; var includeInSolution = true; foreach (var projectReference in node.ProjectReferenceItems) { if (string.Equals( projectReference.GetMetadataValue("IncludeInSolution"), "false", StringComparison.OrdinalIgnoreCase)) { includeInSolution = false; break; } projectConfigurationPlatform = projectConfigurationPlatform .WithConfiguration(projectReference.GetMetadataValue("Configuration")) .WithPlatform(projectReference.GetMetadataValue("Platform")); var _solutionFolder = projectReference.GetMetadataValue("SolutionFolder"); if (!string.IsNullOrEmpty(_solutionFolder)) { solutionFolder = _solutionFolder; } } if (!includeInSolution) { continue; } var solutionNode = solution.AddProject( node.ProjectPath, solutionFolder, projectGuid); solutionNode.AddConfigurationMap(new SolutionConfigurationPlatformMap( solutionConfigurationPlatform, projectConfigurationPlatform)); } } return(solution); }