Skip to content
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 @@ -4,6 +4,7 @@
#nullable disable

using System.CommandLine;
using Microsoft.Build.Definition;
using Microsoft.Build.Evaluation;
using Microsoft.Build.Execution;
using Microsoft.DotNet.Cli.Commands.Hidden.List.Reference;
Expand Down Expand Up @@ -49,7 +50,11 @@ public override int Execute()
return 0;
}

ProjectInstance projectInstance = new(msbuildProj.ProjectRootElement);
// Only ProjectReference items (and no targets) are read below, so stop after the Items pass
// instead of running a full evaluation.
ProjectInstance projectInstance = ProjectInstance.FromProjectRootElement(
msbuildProj.ProjectRootElement,
new ProjectOptions { EvaluationStage = ProjectEvaluationStage.Items });
Reporter.Output.WriteLine($"{CliStrings.ProjectReferenceOneOrMore}");
Reporter.Output.WriteLine(new string('-', CliStrings.ProjectReferenceOneOrMore.Length));
foreach (var item in projectInstance.GetItems("ProjectReference"))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
using System.CommandLine;
using System.Diagnostics;
using Microsoft.Build.Construction;
using Microsoft.Build.Definition;
using Microsoft.Build.Evaluation;
using Microsoft.Build.Exceptions;
using Microsoft.Build.Execution;
using Microsoft.DotNet.Cli.Extensions;
Expand Down Expand Up @@ -174,7 +176,12 @@ private void AddProject(SolutionModel solution, string fullProjectPath, ISolutio
return;
}

ProjectInstance projectInstance = new ProjectInstance(projectRootElement);
// Only evaluated properties (ProjectGuid, Platforms, Configurations, DefaultProjectTypeGuid) and
// ProjectReference items are read from this instance, never targets, so stop after the Items pass
// instead of running a full evaluation.
ProjectInstance projectInstance = ProjectInstance.FromProjectRootElement(
projectRootElement,
new ProjectOptions { EvaluationStage = ProjectEvaluationStage.Items });

string projectTypeGuid = solution.ProjectTypes.FirstOrDefault(t => t.Extension == Path.GetExtension(fullProjectPath))?.ProjectTypeId.ToString()
?? projectRootElement.GetProjectTypeGuid() ?? projectInstance.GetDefaultProjectTypeGuid();
Expand Down
31 changes: 31 additions & 0 deletions src/Cli/dotnet/Extensions/ProjectInstanceExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// The .NET Foundation licenses this file to you under the MIT license.

using Microsoft.Build.Execution;
using NuGet.Frameworks;

namespace Microsoft.DotNet.Cli.Extensions;

Expand Down Expand Up @@ -47,4 +48,34 @@ public static IEnumerable<string> GetConfigurations(this ProjectInstance project
.Where(c => !string.IsNullOrWhiteSpace(c))
.DefaultIfEmpty("Debug");
}

public static IEnumerable<string> GetRuntimeIdentifiers(this ProjectInstance projectInstance)
{
return projectInstance
.GetPropertyCommaSeparatedValues("RuntimeIdentifier")
.Concat(projectInstance.GetPropertyCommaSeparatedValues("RuntimeIdentifiers"))
.Select(value => value.ToLower())
.Distinct();
}

public static IEnumerable<NuGetFramework> GetTargetFrameworks(this ProjectInstance projectInstance)
{
var targetFrameworksStrings = projectInstance
.GetPropertyCommaSeparatedValues("TargetFramework")
.Union(projectInstance.GetPropertyCommaSeparatedValues("TargetFrameworks"))
.Select((value) => value.ToLower());

var uniqueTargetFrameworkStrings = new HashSet<string>(targetFrameworksStrings);

return uniqueTargetFrameworkStrings
.Select((frameworkString) => NuGetFramework.Parse(frameworkString));
}

public static IEnumerable<string> GetPropertyCommaSeparatedValues(this ProjectInstance projectInstance, string propertyName)
{
return projectInstance.GetPropertyValue(propertyName)
.Split(';')
.Select((value) => value.Trim())
.Where((value) => !string.IsNullOrEmpty(value));
}
}
37 changes: 24 additions & 13 deletions src/Cli/dotnet/MsbuildProject.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@

#if !CLI_AOT
using Microsoft.Build.Construction;
using Microsoft.Build.Definition;
using Microsoft.Build.Evaluation;
using Microsoft.Build.Execution;
using Microsoft.Build.Exceptions;
using Microsoft.Build.Framework;
using Microsoft.Build.Logging;
Expand Down Expand Up @@ -40,6 +42,7 @@ public static bool TryGetProjectFileFromDirectory(string projectDirectory, [NotN
private List<NuGetFramework> _cachedTfms = null;
private IEnumerable<string> cachedRuntimeIdentifiers;
private IEnumerable<string> cachedConfigurations;
private ProjectInstance _cachedEvaluatedProject;
private readonly bool _interactive = false;

private MsbuildProject(ProjectCollection projects, ProjectRootElement project, bool interactive)
Expand Down Expand Up @@ -153,7 +156,7 @@ public IEnumerable<NuGetFramework> GetTargetFrameworks()

public IEnumerable<string> GetConfigurations()
{
return cachedConfigurations ??= GetEvaluatedProject().GetConfigurations();
return cachedConfigurations ??= GetEvaluatedProject().GetPropertyCommaSeparatedValues("Configurations");
}

public bool CanWorkOnFramework(NuGetFramework framework)
Expand Down Expand Up @@ -182,29 +185,37 @@ public bool IsTargetingFramework(NuGetFramework framework)
return false;
}

private Project GetEvaluatedProject()
private ProjectInstance GetEvaluatedProject()
{
// Reuse a single evaluation across the RID/TFM/Configuration getters. A ProjectInstance is a
// standalone snapshot that is never stored in the ProjectCollection's loaded-project cache, so
// (unlike a cached Project) it can never be silently re-evaluated to Full by a later LoadProject.
if (_cachedEvaluatedProject is not null)
{
return _cachedEvaluatedProject;
}

try
{
Project project;
// Only evaluated properties (RuntimeIdentifier(s), TargetFramework(s), Configurations) are read
// from the returned instance, never items or targets, so stop after the Properties pass instead
// of running a full evaluation.
var options = new ProjectOptions
{
ProjectCollection = _projects,
EvaluationStage = ProjectEvaluationStage.Properties,
};
if (_interactive)
{
// NuGet need this environment variable to call plugin dll
Environment.SetEnvironmentVariable("DOTNET_HOST_PATH", new Muxer().MuxerPath);
// Even during evaluation time, the SDK resolver may need to output auth instructions, so set a logger.
_projects.RegisterLogger(new ConsoleLogger(LoggerVerbosity.Minimal));
project = _projects.LoadProject(
ProjectRootElement.FullPath,
new Dictionary<string, string>
{ ["NuGetInteractive"] = "true" },
null);
}
else
{
project = _projects.LoadProject(ProjectRootElement.FullPath);
options.GlobalProperties = new Dictionary<string, string>
{ ["NuGetInteractive"] = "true" };
}

return project;
return _cachedEvaluatedProject = ProjectInstance.FromFile(ProjectRootElement.FullPath, options);
}
catch (InvalidProjectFileException e)
{
Expand Down
11 changes: 10 additions & 1 deletion src/Cli/dotnet/ReleasePropertyProjectLocator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using System.CommandLine;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using Microsoft.Build.Definition;
using Microsoft.Build.Evaluation;
using Microsoft.Build.Execution;
using Microsoft.DotNet.Cli.Commands.Run;
Expand Down Expand Up @@ -241,7 +242,15 @@ private bool IsUnanalyzableProjectInSolution(SolutionProjectModel project, strin
{
try
{
return new ProjectInstance(projectPath, globalProperties, "Current");
// Only evaluated property values (PublishRelease/PackRelease and FullPath) are read from the
// returned instance, never items or targets, so stop after the Properties pass instead of
// running a full evaluation (which additionally globs items and registers targets).
return ProjectInstance.FromFile(projectPath, new ProjectOptions
{
GlobalProperties = globalProperties,
ToolsVersion = "Current",
EvaluationStage = ProjectEvaluationStage.Properties,
});
}
catch (Exception e) // Catch failed file access, or invalid project files that cause errors when read into memory,
{
Expand Down
Loading