Skip to content

Commit

Permalink
Load projects referenced through ProjectReference with ReferenceOutpu…
Browse files Browse the repository at this point in the history
…tAssembly=false to MSBuildWorkspace (#73285)
  • Loading branch information
tmat authored Apr 30, 2024
1 parent acb75e2 commit f223c7a
Show file tree
Hide file tree
Showing 5 changed files with 64 additions and 24 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@ internal static class Extensions
public static IEnumerable<ProjectFileReference> GetProjectReferences(this MSB.Execution.ProjectInstance executedProject)
=> executedProject
.GetItems(ItemNames.ProjectReference)
.Where(i => i.ReferenceOutputAssemblyIsTrue())
.Select(CreateProjectFileReference);

public static ImmutableArray<PackageReference> GetPackageReferences(this MSB.Execution.ProjectInstance executedProject)
Expand All @@ -55,7 +54,7 @@ public static ImmutableArray<PackageReference> GetPackageReferences(this MSB.Exe
/// Create a <see cref="ProjectFileReference"/> from a ProjectReference node in the MSBuild file.
/// </summary>
private static ProjectFileReference CreateProjectFileReference(MSB.Execution.ProjectItemInstance reference)
=> new(reference.EvaluatedInclude, reference.GetAliases());
=> new(reference.EvaluatedInclude, reference.GetAliases(), reference.ReferenceOutputAssemblyIsTrue());

public static ImmutableArray<string> GetAliases(this MSB.Framework.ITaskItem item)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,19 @@ internal sealed class ProjectFileReference
[DataMember(Order = 1)]
public ImmutableArray<string> Aliases { get; }

public ProjectFileReference(string path, ImmutableArray<string> aliases)
/// <summary>
/// The value of <see cref="MetadataNames.ReferenceOutputAssembly"/>.
/// </summary>
[DataMember(Order = 2)]
public bool ReferenceOutputAssembly { get; }

public ProjectFileReference(string path, ImmutableArray<string> aliases, bool referenceOutputAssembly)
{
Debug.Assert(!aliases.IsDefault);

this.Path = path;
this.Aliases = aliases;
Path = path;
Aliases = aliases;
ReferenceOutputAssembly = referenceOutputAssembly;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -209,26 +209,35 @@ private async Task<ResolvedReferences> ResolveReferencesAsync(ProjectId id, Proj
continue;
}

// If we don't know how to load a project (that is, it's not a language we support), we can still
// attempt to verify that its output exists on disk and is included in our set of metadata references.
// If it is, we'll just leave it in place.
if (!IsProjectLoadable(projectReferencePath) &&
await VerifyUnloadableProjectOutputExistsAsync(projectReferencePath, builder, cancellationToken).ConfigureAwait(false))
if (projectFileReference.ReferenceOutputAssembly)
{
continue;
}

// If metadata is preferred, see if the project reference's output exists on disk and is included
// in our metadata references. If it is, don't create a project reference; we'll just use the metadata.
if (_preferMetadataForReferencesOfDiscoveredProjects &&
await VerifyProjectOutputExistsAsync(projectReferencePath, builder, cancellationToken).ConfigureAwait(false))
{
continue;
// If we don't know how to load a project (that is, it's not a language we support), we can still
// attempt to verify that its output exists on disk and is included in our set of metadata references.
// If it is, we'll just leave it in place.
if (!IsProjectLoadable(projectReferencePath) &&
await VerifyUnloadableProjectOutputExistsAsync(projectReferencePath, builder, cancellationToken).ConfigureAwait(false))
{
continue;
}

// If metadata is preferred, see if the project reference's output exists on disk and is included
// in our metadata references. If it is, don't create a project reference; we'll just use the metadata.
if (_preferMetadataForReferencesOfDiscoveredProjects &&
await VerifyProjectOutputExistsAsync(projectReferencePath, builder, cancellationToken).ConfigureAwait(false))
{
continue;
}

// Finally, we'll try to load and reference the project.
if (await TryLoadAndAddReferenceAsync(id, projectReferencePath, aliases, builder, cancellationToken).ConfigureAwait(false))
{
continue;
}
}

// Finally, we'll try to load and reference the project.
if (await TryLoadAndAddReferenceAsync(id, projectReferencePath, aliases, builder, cancellationToken).ConfigureAwait(false))
else
{
// Load the project but do not add a reference:
_ = await LoadProjectInfosFromPathAsync(projectReferencePath, _discoveredProjectOptions, cancellationToken).ConfigureAwait(false);
continue;
}
}
Expand Down
4 changes: 3 additions & 1 deletion src/Workspaces/Core/MSBuild/MSBuild/MSBuildWorkspace.cs
Original file line number Diff line number Diff line change
Expand Up @@ -647,7 +647,9 @@ protected override void ApplyProjectReferenceAdded(ProjectId projectId, ProjectR
var project = this.CurrentSolution.GetProject(projectReference.ProjectId);
if (project?.FilePath is not null)
{
_applyChangesProjectFile.AddProjectReferenceAsync(project.Name, new ProjectFileReference(project.FilePath, projectReference.Aliases), CancellationToken.None).Wait();
// Only "ReferenceOutputAssembly=true" project references are represented in the workspace:
var reference = new ProjectFileReference(project.FilePath, projectReference.Aliases, referenceOutputAssembly: true);
_applyChangesProjectFile.AddProjectReferenceAsync(project.Name, reference, CancellationToken.None).Wait();
}

this.OnProjectReferenceAdded(projectId, projectReference);
Expand Down
25 changes: 24 additions & 1 deletion src/Workspaces/MSBuildTest/VisualStudioMSBuildWorkspaceTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2432,7 +2432,7 @@ public async Task TestProjectReferenceWithExternAlias()
}

[ConditionalFact(typeof(VisualStudioMSBuildInstalled))]
public async Task TestProjectReferenceWithReferenceOutputAssemblyFalse()
public async Task TestProjectReferenceWithReferenceOutputAssemblyFalse_SolutionRoot()
{
var files = GetProjectReferenceSolutionFiles();
files = VisitProjectReferences(
Expand All @@ -2451,6 +2451,29 @@ public async Task TestProjectReferenceWithReferenceOutputAssemblyFalse()
}
}

[ConditionalFact(typeof(VisualStudioMSBuildInstalled))]
public async Task TestProjectReferenceWithReferenceOutputAssemblyFalse_ProjectRoot()
{
var files = GetProjectReferenceSolutionFiles();
files = VisitProjectReferences(
files,
r => r.Add(new XElement(XName.Get("ReferenceOutputAssembly", MSBuildNamespace), "false")));

CreateFiles(files);

var referencingProjectPath = GetSolutionFileName(@"CSharpProject\CSharpProject_ProjectReference.csproj");
var referencedProjectPath = GetSolutionFileName(@"CSharpProject\CSharpProject.csproj");

using var workspace = CreateMSBuildWorkspace();
var project = await workspace.OpenProjectAsync(referencingProjectPath);

Assert.Empty(project.ProjectReferences);

// Project referenced through ProjectReference with ReferenceOutputAssembly=false
// should be present in the solution.
Assert.NotNull(project.Solution.GetProjectsByName("CSharpProject").SingleOrDefault());
}

private static FileSet VisitProjectReferences(FileSet files, Action<XElement> visitProjectReference)
{
var result = new List<(string, object)>();
Expand Down

0 comments on commit f223c7a

Please sign in to comment.