提交 e18001b0 编写于 作者: M mattwar

Add code to MSBuildWorkspace to convert metadata references into project...

Add code to MSBuildWorkspace to convert metadata references into project references if the metadata reference is to an assembly that matches a project's output path.  This help recover from failures to interpret project files correctly.

Change CustomWorkspace.AddXXX methods to return the solution/project/document they are adding. (changeset 1289678)
上级 3e338d06
......@@ -34,7 +34,7 @@ public static ProjectInfo CreateProjectInfo(Workspace workspace, string projectN
// TODO (tomat): to match csc.exe/vbc.exe we should use CommonCommandLineCompiler.ExistingReferencesResolver to deal with #r's
var referenceResolver = new MetadataFileReferenceResolver(commandLineArguments.ReferencePaths, commandLineArguments.BaseDirectory);
var referenceProvider = MetadataFileReferenceProvider.Default;
var referenceProvider = workspace.Services.GetService<IMetadataReferenceProviderService>().GetProvider();
var xmlFileResolver = new XmlFileResolver(commandLineArguments.BaseDirectory);
var strongNameProvider = new DesktopStrongNameProvider(commandLineArguments.KeyFileSearchPaths);
......
......@@ -36,7 +36,7 @@ public new void ClearSolution()
/// <summary>
/// Adds an entire solution to the workspace, replacing any existing solution.
/// </summary>
public void AddSolution(SolutionInfo solutionInfo)
public Solution AddSolution(SolutionInfo solutionInfo)
{
if (solutionInfo == null)
{
......@@ -44,22 +44,23 @@ public void AddSolution(SolutionInfo solutionInfo)
}
this.OnSolutionAdded(solutionInfo);
return this.CurrentSolution;
}
/// <summary>
/// Adds a project to the workspace. All previous projects remain intact.
/// </summary>
public ProjectId AddProject(string name, string language)
public Project AddProject(string name, string language)
{
var info = ProjectInfo.Create(ProjectId.CreateNewId(), VersionStamp.Create(), name, name, language);
this.AddProject(info);
return info.Id;
return this.AddProject(info);
}
/// <summary>
/// Adds a project to the workspace. All previous projects remain intact.
/// </summary>
public void AddProject(ProjectInfo projectInfo)
public Project AddProject(ProjectInfo projectInfo)
{
if (projectInfo == null)
{
......@@ -67,6 +68,8 @@ public void AddProject(ProjectInfo projectInfo)
}
this.OnProjectAdded(projectInfo);
return this.CurrentSolution.GetProject(projectInfo.Id);
}
/// <summary>
......@@ -89,7 +92,7 @@ public void AddProjects(IEnumerable<ProjectInfo> projectInfos)
/// <summary>
/// Adds a document to the workspace.
/// </summary>
public DocumentId AddDocument(ProjectId projectId, string name, SourceText text)
public Document AddDocument(ProjectId projectId, string name, SourceText text)
{
if (projectId == null)
{
......@@ -109,15 +112,13 @@ public DocumentId AddDocument(ProjectId projectId, string name, SourceText text)
var id = DocumentId.CreateNewId(projectId);
var loader = TextLoader.From(TextAndVersion.Create(text, VersionStamp.Create()));
this.AddDocument(DocumentInfo.Create(id, name, loader: loader));
return id;
return this.AddDocument(DocumentInfo.Create(id, name, loader: loader));
}
/// <summary>
/// Adds a document to the workspace.
/// </summary>
public void AddDocument(DocumentInfo documentInfo)
public Document AddDocument(DocumentInfo documentInfo)
{
if (documentInfo == null)
{
......@@ -125,6 +126,8 @@ public void AddDocument(DocumentInfo documentInfo)
}
this.OnDocumentAdded(documentInfo);
return this.CurrentSolution.GetDocument(documentInfo.Id);
}
}
}
......@@ -420,6 +420,8 @@ public async Task<Solution> OpenSolutionAsync(string solutionFilePath, Cancellat
// construct workspace from loaded project infos
this.OnSolutionAdded(SolutionInfo.Create(SolutionId.CreateNewId(debugName: absoluteSolutionPath), version, absoluteSolutionPath, loadedProjects));
this.UpdateReferencesAfterAdd();
return this.CurrentSolution;
}
......@@ -448,6 +450,8 @@ public async Task<Project> OpenProjectAsync(string projectFilePath, Cancellation
this.OnProjectAdded(project);
}
this.UpdateReferencesAfterAdd();
return this.CurrentSolution.GetProject(projectId);
}
}
......@@ -456,6 +460,71 @@ public async Task<Project> OpenProjectAsync(string projectFilePath, Cancellation
return null;
}
private void UpdateReferencesAfterAdd()
{
using (this.serializationLock.DisposableWait())
{
var oldSolution = this.CurrentSolution;
var newSolution = this.UpdateReferencesAfterAdd(oldSolution);
if (newSolution != oldSolution)
{
newSolution = this.SetCurrentSolution(newSolution);
var ignore = this.RaiseWorkspaceChangedEventAsync(WorkspaceChangeKind.SolutionChanged, oldSolution, newSolution);
}
}
}
// Updates all projects to properly reference other existing projects via project references instead of using references to built metadata.
private Solution UpdateReferencesAfterAdd(Solution solution)
{
// Build map from output assembly path to ProjectId
// Use explicit loop instead of ToDictionary so we don't throw if multiple projects have same output assembly path.
var outputAssemblyToProjectIdMap = new Dictionary<string, ProjectId>();
foreach (var p in solution.Projects)
{
if (!string.IsNullOrEmpty(p.OutputFilePath))
{
outputAssemblyToProjectIdMap[p.OutputFilePath] = p.Id;
}
}
// now fix each project if necessary
foreach (var pid in solution.ProjectIds)
{
var project = solution.GetProject(pid);
// convert metadata references to project references if the metadata reference matches some project's output assembly.
foreach (var meta in project.MetadataReferences)
{
var pemeta = meta as PortableExecutableReference;
if (pemeta != null)
{
ProjectId matchingProjectId;
// check both Display and FilePath. FilePath points to the actually bits, but Display should match output path if
// the metadata reference is shadow copied.
if ((!string.IsNullOrEmpty(pemeta.Display) && outputAssemblyToProjectIdMap.TryGetValue(pemeta.Display, out matchingProjectId)) ||
(!string.IsNullOrEmpty(pemeta.FilePath) && outputAssemblyToProjectIdMap.TryGetValue(pemeta.FilePath, out matchingProjectId)))
{
var newProjRef = new ProjectReference(matchingProjectId, pemeta.Properties.Aliases, pemeta.Properties.EmbedInteropTypes);
if (!project.ProjectReferences.Contains(newProjRef))
{
project = project.WithProjectReferences(project.ProjectReferences.Concat(newProjRef));
}
project = project.WithMetadataReferences(project.MetadataReferences.Where(mr => mr != meta));
}
}
}
solution = project.Solution;
}
return solution;
}
private async Task<ProjectId> GetOrLoadProjectAsync(string projectFilePath, IProjectFileLoader loader, bool preferMetadata, List<ProjectInfo> loadedProjects, CancellationToken cancellationToken)
{
var projectId = GetProjectId(projectFilePath);
......
......@@ -23,7 +23,140 @@ namespace Microsoft.CodeAnalysis.UnitTests
public partial class CustomWorkspaceTests : WorkspaceTestBase
{
[Fact, Trait(Traits.Feature, Traits.Features.Workspace)]
public void TestAddProject_FromCommandLineArgs()
public void TestAddProject_ProjectInfo()
{
var info = ProjectInfo.Create(
ProjectId.CreateNewId(),
version: VersionStamp.Default,
name: "TestProject",
assemblyName: "TestProject.dll",
language: LanguageNames.CSharp);
using (var ws = new CustomWorkspace())
{
var project = ws.AddProject(info);
Assert.Equal(project, ws.CurrentSolution.Projects.FirstOrDefault());
Assert.Equal(info.Name, project.Name);
Assert.Equal(info.Id, project.Id);
Assert.Equal(info.AssemblyName, project.AssemblyName);
Assert.Equal(info.Language, project.Language);
}
}
[Fact, Trait(Traits.Feature, Traits.Features.Workspace)]
public void TestAddProject_NameAndLanguage()
{
using (var ws = new CustomWorkspace())
{
var project = ws.AddProject("TestProject", LanguageNames.CSharp);
Assert.Same(project, ws.CurrentSolution.Projects.FirstOrDefault());
Assert.Equal("TestProject", project.Name);
Assert.Equal(LanguageNames.CSharp, project.Language);
}
}
[Fact, Trait(Traits.Feature, Traits.Features.Workspace)]
public void TestAddDocument_DocumentInfo()
{
using (var ws = new CustomWorkspace())
{
var project = ws.AddProject("TestProject", LanguageNames.CSharp);
var info = DocumentInfo.Create(DocumentId.CreateNewId(project.Id), "code.cs");
var doc = ws.AddDocument(info);
Assert.Equal(ws.CurrentSolution.GetDocument(info.Id), doc);
Assert.Equal(info.Name, doc.Name);
}
}
[Fact, Trait(Traits.Feature, Traits.Features.Workspace)]
public void TestAddDocument_NameAndText()
{
using (var ws = new CustomWorkspace())
{
var project = ws.AddProject("TestProject", LanguageNames.CSharp);
var name = "code.cs";
var source = "class C {}";
var doc = ws.AddDocument(project.Id, name, SourceText.From(source));
Assert.Equal(name, doc.Name);
Assert.Equal(source, doc.GetTextAsync().Result.ToString());
}
}
[Fact, Trait(Traits.Feature, Traits.Features.Workspace)]
public void TestAddSolution_SolutionInfo()
{
using (var ws = new CustomWorkspace())
{
var pinfo = ProjectInfo.Create(
ProjectId.CreateNewId(),
version: VersionStamp.Default,
name: "TestProject",
assemblyName: "TestProject.dll",
language: LanguageNames.CSharp);
var sinfo = SolutionInfo.Create(SolutionId.CreateNewId(), VersionStamp.Default, projects: new ProjectInfo[] { pinfo });
var solution = ws.AddSolution(sinfo);
Assert.Same(ws.CurrentSolution, solution);
Assert.Equal(solution.Id, sinfo.Id);
Assert.Equal(sinfo.Projects.Count, solution.ProjectIds.Count);
var project = solution.Projects.FirstOrDefault();
Assert.NotNull(project);
Assert.Equal(pinfo.Name, project.Name);
Assert.Equal(pinfo.Id, project.Id);
Assert.Equal(pinfo.AssemblyName, project.AssemblyName);
Assert.Equal(pinfo.Language, project.Language);
}
}
[Fact, Trait(Traits.Feature, Traits.Features.Workspace)]
public void TestAddProjects()
{
var id1 = ProjectId.CreateNewId();
var info1 = ProjectInfo.Create(
id1,
version: VersionStamp.Default,
name: "TestProject1",
assemblyName: "TestProject1.dll",
language: LanguageNames.CSharp);
var id2 = ProjectId.CreateNewId();
var info2 = ProjectInfo.Create(
id2,
version: VersionStamp.Default,
name: "TestProject2",
assemblyName: "TestProject2.dll",
language: LanguageNames.VisualBasic,
projectReferences: new[] { new ProjectReference(id1) });
using (var ws = new CustomWorkspace())
{
ws.AddProjects(new[] { info1, info2 });
var solution = ws.CurrentSolution;
Assert.Equal(2, solution.ProjectIds.Count);
var project1 = solution.GetProject(id1);
Assert.Equal(info1.Name, project1.Name);
Assert.Equal(info1.Id, project1.Id);
Assert.Equal(info1.AssemblyName, project1.AssemblyName);
Assert.Equal(info1.Language, project1.Language);
var project2 = solution.GetProject(id2);
Assert.Equal(info2.Name, project2.Name);
Assert.Equal(info2.Id, project2.Id);
Assert.Equal(info2.AssemblyName, project2.AssemblyName);
Assert.Equal(info2.Language, project2.Language);
Assert.Equal(1, project2.ProjectReferences.Count());
Assert.Equal(id1, project2.ProjectReferences.First().ProjectId);
}
}
[Fact, Trait(Traits.Feature, Traits.Features.Workspace)]
public void TestAddProject_CommandLineProject()
{
CreateFiles(GetSimpleCSharpSolutionFiles());
......
......@@ -924,6 +924,33 @@ public void TestOpenProject_WithReferencedProject_LoadMetadata_NonExistentMetada
Assert.False(metaRefs.Any(r => !r.Properties.Aliases.IsDefault && r.Properties.Aliases.Contains("CSharpProject")));
}
[Fact, Trait(Traits.Feature, Traits.Features.Workspace)]
public void TestOpenProject_UpdateExistingReferences()
{
CreateFiles(GetMultiProjectSolutionFiles()
.WithFile(@"CSharpProject\bin\Debug\CSharpProject.dll", GetResourceBytes("CSharpProject.dll")));
// keep metadata reference from holding files open
Workspace.TestHookStandaloneProjectsDoNotHoldReferences = true;
// first open vb project that references c# project, but only reference the c# project's built metadata
var ws = MSBuildWorkspace.Create();
ws.LoadMetadataForReferencedProjects = true;
var vbproject = ws.OpenProjectAsync(GetSolutionFileName(@"VisualBasicProject\VisualBasicProject.vbproj")).Result;
// prove vb project references c# project as a metadata reference
Assert.Equal(0, vbproject.ProjectReferences.Count());
Assert.Equal(true, vbproject.MetadataReferences.Any(r => r is PortableExecutableReference && ((PortableExecutableReference)r).Display.Contains("CSharpProject.dll")));
// now expliticly open the c# project that got referenced as metadata
var csproject = ws.OpenProjectAsync(GetSolutionFileName(@"CSharpProject\CSharpProject.csproj")).Result;
// show that the vb project now references the c# project directly (not as metadata)
vbproject = ws.CurrentSolution.GetProject(vbproject.Id);
Assert.Equal(1, vbproject.ProjectReferences.Count());
Assert.False(vbproject.MetadataReferences.Any(r => !r.Properties.Aliases.IsDefault && r.Properties.Aliases.Contains("CSharpProject")));
}
[ConditionalFact(typeof(Framework35Installed))]
[Trait(Traits.Feature, Traits.Features.Workspace)]
[WorkItem(528984, "DevDiv")]
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册