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

Add option to serialize global properties when running static graph-based restore #5413

Merged
123 changes: 117 additions & 6 deletions src/NuGet.Core/NuGet.Build.Tasks.Console/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,10 @@
using System.Globalization;
using System.IO;
using System.Linq;
#if NETFRAMEWORK
using System.Reflection;
#endif
using System.Text;
using System.Threading.Tasks;

namespace NuGet.Build.Tasks.Console
Expand Down Expand Up @@ -46,7 +49,7 @@ public static async Task<int> Main(string[] args)
NuGet.Common.Migrations.MigrationRunner.Run();

// Parse command-line arguments
if (!TryParseArguments(args, out (Dictionary<string, string> Options, FileInfo MSBuildExeFilePath, string EntryProjectFilePath, Dictionary<string, string> MSBuildGlobalProperties) arguments))
if (!TryParseArguments(args, () => System.Console.OpenStandardInput(), System.Console.Error, out (Dictionary<string, string> Options, FileInfo MSBuildExeFilePath, string EntryProjectFilePath, Dictionary<string, string> MSBuildGlobalProperties) arguments))
{
return 1;
}
Expand Down Expand Up @@ -144,23 +147,62 @@ private static Dictionary<string, string> ParseSemicolonDelimitedListOfKeyValueP
/// Parses command-line arguments.
/// </summary>
/// <param name="args">A <see cref="T:string[]" /> containing the process command-line arguments.</param>
/// <param name="getStream">A <see cref="Func{TResult}" /> that is called to get a <see cref="Stream" /> to read.</param>
/// <param name="errorWriter">A <see cref="TextWriter" /> to write errors to.</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 TryParseArguments(string[] args, out (Dictionary<string, string> Options, FileInfo MSBuildExeFilePath, string EntryProjectFilePath, Dictionary<string, string> MSBuildGlobalProperties) arguments)
internal static bool TryParseArguments(string[] args, Func<Stream> getStream, TextWriter errorWriter, out (Dictionary<string, string> Options, FileInfo MSBuildExeFilePath, string EntryProjectFilePath, Dictionary<string, string> MSBuildGlobalProperties) arguments)
{
if (args.Length != 4)
arguments = (null, null, null, null);

// This application receives 3 or 4 arguments:
// 1. A semicolon delimited list of key value pairs that are the options to the program
// 2. The full path to MSBuild.exe
// 3. The full path to the entry project file
// 4. (optional) A semicolon delimited list of key value pairs that are the global properties to pass to MSBuild
if (args.Length < 3 || args.Length > 4)
{
arguments = (null, null, null, null);
// An error occurred parsing command-line arguments in static graph-based restore as there was an unexpected number of arguments, {0}. Please file an issue at https://github.com/NuGet/Home. {0}
LogError(errorWriter, Strings.Error_StaticGraphRestoreArgumentParsingFailedInvalidNumberOfArguments, args.Length);

return false;
}

try
{
var options = ParseSemicolonDelimitedListOfKeyValuePairs(args[0]);
Dictionary<string, string> options = ParseSemicolonDelimitedListOfKeyValuePairs(args[0]);
var msbuildExeFilePath = new FileInfo(args[1]);
var entryProjectFilePath = args[2];
var globalProperties = ParseSemicolonDelimitedListOfKeyValuePairs(args[3]);

Dictionary<string, string> globalProperties = null;

// If there are 3 arguments then the global properties will be read from STDIN
if (args.Length == 3)
{
#if NETFRAMEWORK
if (AppDomain.CurrentDomain.IsDefaultAppDomain())
{
// The application is called twice and the first invocation does not need to read the global properties
jeffkl marked this conversation as resolved.
Show resolved Hide resolved
globalProperties = null;
}
else
{
#endif
using var reader = new BinaryReader(getStream(), new UTF8Encoding(encoderShouldEmitUTF8Identifier: false, throwOnInvalidBytes: true), leaveOpen: true);
jeffkl marked this conversation as resolved.
Show resolved Hide resolved

if (!TryDeserializeGlobalProperties(errorWriter, reader, out globalProperties))
{
// An error will have already been logged by TryDeserializeGlobalProperties()
return false;
}
#if NETFRAMEWORK
}
#endif
}
else
{
globalProperties = ParseSemicolonDelimitedListOfKeyValuePairs(args[3]);
}

arguments = (options, msbuildExeFilePath, entryProjectFilePath, globalProperties);

Expand All @@ -174,5 +216,74 @@ private static bool TryParseArguments(string[] args, out (Dictionary<string, str
return false;
}
}

/// <summary>
/// Logs an error to the specified <see cref="TextWriter" />.
/// </summary>
/// <param name="errorWriter">The <see cref="TextWriter" /> to write the error to.</param>
/// <param name="format">The formatted string of the error.</param>
/// <param name="args">An object array of zero or more objects to format with the error message.</param>
private static void LogError(TextWriter errorWriter, string format, params object[] args)
{
errorWriter.WriteLine(format, args);
}

/// <summary>
/// Attempts to deserialize global properties from the standard input stream.
/// </summary>
/// <remarks>
/// <param name="errorWriter">A <see cref="TextWriter" /> to write errors to if one occurs.</param>
/// <param name="reader">The <see cref="BinaryReader" /> to use when deserializing the arguments.</param>
/// <param name="globalProperties">Receives a <see cref="Dictionary{TKey, TValue}" /> representing the global properties.</param>
/// <returns><see langword="true" /> if the arguments were successfully deserialized, otherwise <see langword="false" />.</returns>
internal static bool TryDeserializeGlobalProperties(TextWriter errorWriter, BinaryReader reader, out Dictionary<string, string> globalProperties)
jeffkl marked this conversation as resolved.
Show resolved Hide resolved
{
globalProperties = null;

int count = 0;

try
{
// Read the first integer from the stream which is the number of global properties
count = reader.ReadInt32();
}
catch (Exception e)
{
LogError(errorWriter, Strings.Error_StaticGraphRestoreArgumentsParsingFailedExceptionReadingStream, e.Message, e.ToString());

return false;
}

// If the integer is negative or larger than a short then the value is considered invalid. No user should have anywhere near 32,000+ global properties
// and this should only happen if the bytes in the stream contain completely unexpected values.
if (count < 0 || count > short.MaxValue)
{
// An error occurred parsing command-line arguments in static graph-based restore as the first integer read, {0}, was is greater than the allowable value. Please file an issue at https://github.com/NuGet/Home
LogError(errorWriter, Strings.Error_StaticGraphRestoreArgumentsParsingFailedUnexpectedIntegerValue, count);

return false;
}

globalProperties = new Dictionary<string, string>(capacity: count, StringComparer.OrdinalIgnoreCase);

for (int i = 0; i < count; i++)
{
try
{
globalProperties[reader.ReadString()] = reader.ReadString();
}
catch (Exception e)
{
globalProperties = null;

// An error occurred parsing command-line arguments in static graph-based restore as an exception occurred reading the standard input stream, {0}. Please file an issue at https://github.com/NuGet/Home
LogError(errorWriter, Strings.Error_StaticGraphRestoreArgumentsParsingFailedExceptionReadingStream, e.Message, e.ToString());

return false;
}
}

return true;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -248,19 +248,20 @@ private static NuGetFramework GetNuGetFramework(TargetFrameworkInformation targe
string.Empty :
targetFrameworkInformation._targetPlatformMoniker);
}
}

internal class TargetFrameworkInformation
{
internal readonly string _targetFrameworkAlias;
internal readonly string _targetFrameworkMoniker;
internal readonly string _targetPlatformMoniker;

public TargetFrameworkInformation(string targetFrameworkAlias, string targetFrameworkMoniker, string targetPlatformMoniker)
private class TargetFrameworkInformation
jeffkl marked this conversation as resolved.
Show resolved Hide resolved
{
_targetFrameworkAlias = targetFrameworkAlias;
_targetFrameworkMoniker = targetFrameworkMoniker;
_targetPlatformMoniker = targetPlatformMoniker;
internal readonly string _targetFrameworkAlias;
internal readonly string _targetFrameworkMoniker;
internal readonly string _targetPlatformMoniker;

public TargetFrameworkInformation(string targetFrameworkAlias, string targetFrameworkMoniker, string targetPlatformMoniker)
{
_targetFrameworkAlias = targetFrameworkAlias;
_targetFrameworkMoniker = targetFrameworkMoniker;
_targetPlatformMoniker = targetPlatformMoniker;
}
}
}

}
1 change: 1 addition & 0 deletions src/NuGet.Core/NuGet.Build.Tasks/NuGet.RestoreEx.targets
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ Copyright (c) .NET Foundation. All rights reserved.
RestorePackagesConfig="$(RestorePackagesConfig)"
SolutionPath="$(SolutionPath)"
ProcessFileName="$(NuGetConsoleProcessFileName)"
SerializeGlobalProperties="$(RestoreSerializeGlobalProperties)"
jeffkl marked this conversation as resolved.
Show resolved Hide resolved
MSBuildStartupDirectory="$(MSBuildStartupDirectory)">
<Output TaskParameter="EmbedInBinlog" ItemName="EmbedInBinlog" />
</RestoreTaskEx>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@

using System.Runtime.CompilerServices;

[assembly: InternalsVisibleTo("NuGet.Build.Tasks.Console.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100b5fc90e7027f67871e773a8fde8938c81dd402ba65b9201d60593e96c492651e889cc13f1415ebb53fac1131ae0bd333c5ee6021672d9718ea31a8aebd0da0072f25d87dba6fc90ffd598ed4da35e44c398c454307e8e33b8426143daec9f596836f97c8f74750e5975c64e2189f45def46b2a2b1247adc3652bf5c308055da9")]
[assembly: InternalsVisibleTo("NuGet.Build.Tasks.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100b5fc90e7027f67871e773a8fde8938c81dd402ba65b9201d60593e96c492651e889cc13f1415ebb53fac1131ae0bd333c5ee6021672d9718ea31a8aebd0da0072f25d87dba6fc90ffd598ed4da35e44c398c454307e8e33b8426143daec9f596836f97c8f74750e5975c64e2189f45def46b2a2b1247adc3652bf5c308055da9")]
3 changes: 0 additions & 3 deletions src/NuGet.Core/NuGet.Build.Tasks/RestoreTaskEx.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,6 @@

using System;
using System.Collections.Generic;
#if !IS_CORECLR
using System.Reflection;
#endif

namespace NuGet.Build.Tasks
{
Expand Down
Loading