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

Revert functionality to have static graph-based restore write arguments to standard input #5080

Merged
merged 3 commits into from
Mar 9, 2023
Merged
Show file tree
Hide file tree
Changes from 2 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
83 changes: 60 additions & 23 deletions src/NuGet.Core/NuGet.Build.Tasks.Console/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;

Expand All @@ -14,6 +16,16 @@ namespace NuGet.Build.Tasks.Console
/// </summary>
internal static class Program
{
/// <summary>
/// A <see cref="T:char[]" /> containing the equals sign '=' to be used to split key/value pairs that are separated by it.
/// </summary>
private static readonly char[] EqualSign = { '=' };

/// <summary>
/// A <see cref="T:char[]" /> containing the semicolon ';' to be used to split key/value pairs that are separated by it.
/// </summary>
private static readonly char[] Semicolon = { ';' };

/// <summary>
/// The main entry point to the console application.
/// </summary>
Expand All @@ -29,36 +41,34 @@ public static async Task<int> Main(string[] args)
}

NuGet.Common.Migrations.MigrationRunner.Run();
if (args.Length != 2)

// Parse command-line arguments
if (!TryParseArguments(args, out (Dictionary<string, string> Options, FileInfo MSBuildExeFilePath, string EntryProjectFilePath, Dictionary<string, string> MSBuildGlobalProperties) arguments))
{
return 1;
}

var msbuildFilePath = new FileInfo(args[0]);
var entryProjectPath = new FileInfo(args[1]);

// Enable MSBuild feature flags
MSBuildFeatureFlags.MSBuildExeFilePath = msbuildFilePath.FullName;
MSBuildFeatureFlags.MSBuildExeFilePath = arguments.MSBuildExeFilePath.FullName;
MSBuildFeatureFlags.EnableCacheFileEnumerations = true;
MSBuildFeatureFlags.LoadAllFilesAsReadonly = true;
MSBuildFeatureFlags.SkipEagerWildcardEvaluations = true;

#if NETFRAMEWORK
if (AppDomain.CurrentDomain.IsDefaultAppDomain())
{
// MSBuild.exe.config has binding redirects that change from time to time and its very hard to make sure that NuGet.Build.Tasks.Console.exe.config is correct.
// It also can be different per instance of Visual Studio so when running unit tests it always needs to match that instance of MSBuild
// The code below runs this EXE in an AppDomain as if its MSBuild.exe so the assembly search location is next to MSBuild.exe and all binding redirects are used
// allowing this process to evaluate MSBuild projects as if it is MSBuild.exe
Assembly thisAssembly = Assembly.GetExecutingAssembly();
var thisAssembly = Assembly.GetExecutingAssembly();

AppDomain appDomain = AppDomain.CreateDomain(
thisAssembly.FullName,
securityInfo: null,
info: new AppDomainSetup
{
ApplicationBase = msbuildFilePath.DirectoryName,
ConfigurationFile = Path.Combine(msbuildFilePath.DirectoryName, "MSBuild.exe.config")
ApplicationBase = arguments.MSBuildExeFilePath.DirectoryName,
ConfigurationFile = Path.Combine(arguments.MSBuildExeFilePath.DirectoryName, "MSBuild.exe.config")
});

return appDomain
Expand All @@ -68,25 +78,19 @@ public static async Task<int> Main(string[] args)
}
#endif

// Parse command-line arguments
if (!TryGetArguments(out StaticGraphRestoreArguments arguments))
{
return 1;
}

// Check whether the ask is to generate the restore graph file.
if (MSBuildStaticGraphRestore.IsOptionTrue("GenerateRestoreGraphFile", arguments.Options))
{
using (var dependencyGraphSpecGenerator = new MSBuildStaticGraphRestore(debug: debug))
{
return dependencyGraphSpecGenerator.WriteDependencyGraphSpec(entryProjectPath.FullName, arguments.GlobalProperties, arguments.Options) ? 0 : 1;
return dependencyGraphSpecGenerator.WriteDependencyGraphSpec(arguments.EntryProjectFilePath, arguments.MSBuildGlobalProperties, arguments.Options) ? 0 : 1;
}
}

// Otherwise run restore!
using (var dependencyGraphSpecGenerator = new MSBuildStaticGraphRestore(debug: debug))
{
return await dependencyGraphSpecGenerator.RestoreAsync(entryProjectPath.FullName, arguments.GlobalProperties, arguments.Options) ? 0 : 1;
return await dependencyGraphSpecGenerator.RestoreAsync(arguments.EntryProjectFilePath, arguments.MSBuildGlobalProperties, arguments.Options) ? 0 : 1;
}
}

Expand All @@ -99,25 +103,58 @@ private static bool IsDebug()
return string.Equals(Environment.GetEnvironmentVariable("DEBUG_RESTORE_TASK"), bool.TrueString, StringComparison.OrdinalIgnoreCase);
}

/// <summary>
/// Parses a semicolon delimited list of equal sign separated key value pairs.
/// </summary>
/// <param name="value">The string containing a semicolon delimited list of key value pairs to parse.</param>
/// <returns>A <see cref="Dictionary{String,String}" /> containing the list of items as key value pairs.</returns>
private static Dictionary<string, string> ParseSemicolonDelimitedListOfKeyValuePairs(string value)
{
var properties = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);

foreach (var pair in value
.Split(Semicolon, StringSplitOptions.RemoveEmptyEntries)
.Where(i => !string.IsNullOrWhiteSpace(i))
.Select(i => i.Split(EqualSign, 2))
.Where(i => i.Length == 2 && !string.IsNullOrWhiteSpace(i[0]) && !string.IsNullOrWhiteSpace(i[1])))
{
properties[pair[0].Trim()] = pair[1].Trim();
}

return properties;
}

/// <summary>
/// Parses command-line arguments.
/// </summary>
/// <param name="staticGraphRestoreArguments">Receives the arguments as a <see cref="StaticGraphRestoreArguments" />.</param>
/// <param name="args">A <see cref="T:string[]" /> containing the process command-line arguments.</param>
/// <param name="arguments">A <see cref="T:Tuple&lt;Dictionary&lt;string, string&gt;, FileInfo, string, Dictionary&lt;string, string&gt;&gt;" /> that receives the parsed command-line arguments.</param>
/// <returns><code>true</code> if the arguments were successfully parsed, otherwise <code>false</code>.</returns>
private static bool TryGetArguments(out StaticGraphRestoreArguments staticGraphRestoreArguments)
private static bool TryParseArguments(string[] args, out (Dictionary<string, string> Options, FileInfo MSBuildExeFilePath, string EntryProjectFilePath, Dictionary<string, string> MSBuildGlobalProperties) arguments)
{
staticGraphRestoreArguments = null;
if (args.Length != 4)
{
arguments = (null, null, null, null);

return false;
}

try
{
using Stream stream = System.Console.OpenStandardInput();
var options = ParseSemicolonDelimitedListOfKeyValuePairs(args[0]);
var msbuildExeFilePath = new FileInfo(args[1]);
var entryProjectFilePath = args[2];
var globalProperties = ParseSemicolonDelimitedListOfKeyValuePairs(args[3]);

staticGraphRestoreArguments = StaticGraphRestoreArguments.Read(stream, System.Console.InputEncoding);
arguments = (options, msbuildExeFilePath, entryProjectFilePath, globalProperties);

return staticGraphRestoreArguments != null;
// Command-line is correct if no exceptions were thrown and the MSBuild path exists and an entry project were specified
return msbuildExeFilePath.Exists && !string.IsNullOrWhiteSpace(entryProjectFilePath);
}
catch (Exception)
{
arguments = (null, null, null, null);

return false;
}
}
Expand Down
Loading