diff --git a/src/VisualStudio/ProjectBase/ProjectNode.cs b/src/VisualStudio/ProjectBase/ProjectNode.cs index 4467c3b8d0..79df53994c 100644 --- a/src/VisualStudio/ProjectBase/ProjectNode.cs +++ b/src/VisualStudio/ProjectBase/ProjectNode.cs @@ -6701,6 +6701,14 @@ public virtual IVsBuildDependency[] BuildDependencies var projectInfo = ProjectInfo.GetProjectInfo(url); if (projectInfo == null) { + if (node.ReferencedProjectGuid == Guid.Empty) + { + // The referenced project has not been loaded, so we do not know its guid yet. + // Do not register a ProjectInfo with an empty guid: that would be cached by url + // and would prevent the guid from being resolved later on. + Logger.Information($"No BuildDependency for project {this.Caption} on {url}: the guid of the referenced project is unknown"); + continue; + } projectInfo = new ProjectInfo(node.ReferencedProjectGuid, url); } var dependency = new BuildDependency(this, projectInfo.Id); diff --git a/src/VisualStudio/ProjectBase/ProjectReferenceNode.cs b/src/VisualStudio/ProjectBase/ProjectReferenceNode.cs index 803a943a0d..20c60bea25 100644 --- a/src/VisualStudio/ProjectBase/ProjectReferenceNode.cs +++ b/src/VisualStudio/ProjectBase/ProjectReferenceNode.cs @@ -252,6 +252,36 @@ public void DropReferencedProjectCache() referencedProjectIsCached = false; } + /// + /// Assign the guid of the referenced project. + /// + /// + /// SDK style project files do not store the guid of the referenced project, so it can only be + /// resolved when the referenced project has been loaded. This also updates the build dependency, + /// which could not be created when the node was constructed without a guid. + /// + public void UpdateReferencedProjectGuid(Guid guid) + { + if (guid == this.referencedProjectGuid) + { + return; + } + ThreadHelper.ThrowIfNotOnUIThread(); + if (this.buildDependency != null) + { + this.ProjectMgr.RemoveBuildDependency(this.buildDependency); + this.buildDependency = null; + } + this.referencedProjectGuid = guid; + if (guid != Guid.Empty) + { + this.buildDependency = new BuildDependency(this.ProjectMgr, guid); + this.ProjectMgr.AddBuildDependency(this.buildDependency); + } + this.DropReferencedProjectCache(); + this.ReDraw(UIHierarchyElement.Icon); + } + EnvDTE.Property GetPropertySave(string name, bool onConfig) { @@ -399,21 +429,26 @@ public ProjectReferenceNode(ProjectNode root, ProjectElement element) ReferencedProjectName = Path.GetFileNameWithoutExtension(referencedProjectRelativePath); } - // Continue even if project settings cannot be read. - try + // An SDK style project file does not store the guid of the referenced project, so we may + // not have one here, for example when the referenced project has not been loaded yet. + // Do not throw in that case: the node must appear in the hierarchy anyway. The guid is + // filled in later, see XSharpProjectNode.FixReferences() and UpdateReferencedProjectGuid(). + if (!Guid.TryParse(guidString, out var guid)) + { + guid = Guid.Empty; + } + this.referencedProjectGuid = guid; + if (guid != Guid.Empty) { - this.referencedProjectGuid = new Guid(guidString); - this.buildDependency = new BuildDependency(this.ProjectMgr, this.referencedProjectGuid); this.ProjectMgr.AddBuildDependency(this.buildDependency); } - finally + var name = this.ItemNode.GetMetadata(ProjectFileConstants.Name); + if (!string.IsNullOrEmpty(name)) { - //Debug.Assert(this.referencedProjectGuid != Guid.Empty, "Could not retrieve referenced project guidproject file"); - - this.ReferencedProjectName = this.ItemNode.GetMetadata(ProjectFileConstants.Name); - - //Debug.Assert(!String.IsNullOrEmpty(this.referencedProjectName), "Could not retrieve referenced project name form project file"); + // Do not clear the name that was determined above when the metadata could not be written, + // which happens for imported items. + this.ReferencedProjectName = name; } Uri uri = new Uri(this.ProjectMgr.BaseURI.Uri, this.referencedProjectRelativePath); diff --git a/src/VisualStudio/ProjectPackage/Nodes/XSharpProjectNode.cs b/src/VisualStudio/ProjectPackage/Nodes/XSharpProjectNode.cs index bc3de027d9..7ade0d6c35 100644 --- a/src/VisualStudio/ProjectPackage/Nodes/XSharpProjectNode.cs +++ b/src/VisualStudio/ProjectPackage/Nodes/XSharpProjectNode.cs @@ -1490,16 +1490,19 @@ internal bool FixReferences() var refnode = FindProject(completePath); Guid refnodeGuid = Guid.Empty; string refnodeName = child.Caption; - if (refnode == null) + if (refnode != null) + { + refnodeGuid = refnode.ProjectIDGuid; + } + else { // this must be a foreign project reference var projectInfo = ProjectInfo.GetProjectInfo(completePath); if (projectInfo == null) { - if (this.GetProjectGuid(completePath, out refnodeGuid)) + if (this.GetProjectGuid(completePath, out refnodeGuid) && refnodeGuid != Guid.Empty) { projectInfo = new ProjectInfo(refnodeGuid, completePath); - element.SetMetadata(ProjectFileConstants.Project, refnodeGuid.ToString("B").ToUpperInvariant()); } } else @@ -1511,19 +1514,19 @@ internal bool FixReferences() { element.SetMetadata(ProjectFileConstants.Project, refnodeGuid.ToString("B").ToUpperInvariant()); element.SetMetadata(ProjectFileConstants.Name, refnodeName); + sdkref.SaveProperties(); + // The node was created before the referenced project was available, so it has no + // guid and no build dependency yet. + sdkref.UpdateReferencedProjectGuid(refnodeGuid); } else { + Logger.Information($"Could not determine the guid of project {completePath}, referenced by {this.Caption}"); found = false; } - if (found) - { - sdkref.SaveProperties(); - } } - - HasIncompleteReferences = !found; } + HasIncompleteReferences = !found; this.SetProjectFileDirty(false); return found; } diff --git a/src/VisualStudio/ProjectPackage/Nodes/XSharpReferenceContainerNode.cs b/src/VisualStudio/ProjectPackage/Nodes/XSharpReferenceContainerNode.cs index 817266d169..575aa290b8 100644 --- a/src/VisualStudio/ProjectPackage/Nodes/XSharpReferenceContainerNode.cs +++ b/src/VisualStudio/ProjectPackage/Nodes/XSharpReferenceContainerNode.cs @@ -39,17 +39,40 @@ protected override ProjectReferenceNode CreateProjectReferenceNode(ProjectElemen bool changed = false; if (parent.IsSdkProject) { - // already handled in the DependenciesContainer + // An SDK style project file does not store the guid and the name of the referenced project, + // so we resolve them here and store them in the (in memory) project element. The base + // ProjectReferenceNode reads them from there. BeforeSave() removes them again, so they are + // not written to the project file, see XSharpSdkProjectNode.Clean(). name = System.IO.Path.GetFileNameWithoutExtension(path); + guid = null; if (refnode != null) { guid = refnode.ProjectIDGuid.ToString("B"); } else { - guid = null; + // Not one of our projects, or a project that has not been loaded yet + var projectInfo = ProjectInfo.GetProjectInfo(path); + if (projectInfo == null && parent.GetProjectGuid(path, out var foreignGuid) && foreignGuid != Guid.Empty) + { + projectInfo = new ProjectInfo(foreignGuid, path); + } + if (projectInfo != null && projectInfo.Id != Guid.Empty) + { + guid = projectInfo.Id.ToString("B"); + } + } + element.SetMetadata(ProjectFileConstants.Name, name); + if (string.IsNullOrEmpty(guid)) + { + // The referenced project is not available yet. The node is created without a guid and + // FixReferences() completes it when the solution has finished loading. parent.HasIncompleteReferences = true; } + else + { + element.SetMetadata(ProjectFileConstants.Project, guid); + } } else if (string.IsNullOrEmpty(guid) || string.IsNullOrEmpty(name)) { @@ -84,7 +107,17 @@ protected override ProjectReferenceNode CreateProjectReferenceNode(ProjectElemen parent.BuildProject.Save(); } - var node = (XSharpProjectReferenceNode) Activator.CreateInstance(ProjectReferenceType,this.ProjectMgr, element); + XSharpProjectReferenceNode node = null; + try + { + node = (XSharpProjectReferenceNode)Activator.CreateInstance(ProjectReferenceType, this.ProjectMgr, element); + } + catch (Exception e) + { + // Do not let this bubble up: ReferenceContainerNode.CreateReferenceNode() swallows the + // exception without logging it and the reference would disappear from the hierarchy. + Logger.Exception(e, "CreateProjectReferenceNode"); + } ReferenceNode existing = null; if (isDuplicateNode(node, ref existing)) { diff --git a/src/VisualStudio/ProjectPackage/XSharpShellEvents.cs b/src/VisualStudio/ProjectPackage/XSharpShellEvents.cs index 5783cfcd53..112d7cd99e 100644 --- a/src/VisualStudio/ProjectPackage/XSharpShellEvents.cs +++ b/src/VisualStudio/ProjectPackage/XSharpShellEvents.cs @@ -86,17 +86,23 @@ internal void Initialize() #if DEV17 private void SolutionEvents_OnAfterOpenSolution(Solution solution) { + FixIncompleteReferences(); + } + /// + /// Complete the project references of SDK style projects that could not be resolved while the + /// project was loading, because the referenced project was not loaded yet. + /// + private void FixIncompleteReferences() + { foreach (var project in XSharpProjectNode.AllProjects) { if (project.HasIncompleteReferences) { - project.FixReferences(); } } - - } + } #endif private void SolutionEvents_OnBeforeCloseSolution() { @@ -129,6 +135,11 @@ private void SolutionEvents_OnBeforeCloseSolution() private void SolutionEvents_OnAfterBackgroundSolutionLoadComplete() { ThreadHelper.ThrowIfNotOnUIThread(); +#if DEV17 + // Projects may have been loaded after OnAfterOpenSolution, so check again now that all + // projects of the solution are available. + FixIncompleteReferences(); +#endif RestoreDesignerWindows(); RestoreStartupProject(); }