Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Load projects referenced through ProjectReference with ReferenceOutputAssembly=false to MSBuildWorkspace #73285

Merged
merged 1 commit into from
Apr 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
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
Loading