Clean up diagnostics output from compile
- Show a build summary - Parse the msbuild format (the one that most compilers output) into a diagnostic message and then print that. - Fixed formatting of LibraryRange in dependency diagnostics
This commit is contained in:
parent
18fbd3efe7
commit
62e586549d
4 changed files with 528 additions and 38 deletions
415
src/Microsoft.DotNet.Tools.Compiler/CanonicalError.cs
Normal file
415
src/Microsoft.DotNet.Tools.Compiler/CanonicalError.cs
Normal file
|
@ -0,0 +1,415 @@
|
||||||
|
using System;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
|
|
||||||
|
// https://github.com/dotnet/roslyn/blob/master/src/Compilers/Core/MSBuildTask/CanonicalError.cs
|
||||||
|
|
||||||
|
namespace Microsoft.DotNet.Tools.Compiler
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Functions for dealing with the specially formatted errors returned by
|
||||||
|
/// build tools.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Various tools produce and consume CanonicalErrors in various formats.
|
||||||
|
///
|
||||||
|
/// DEVENV Format When Clicking on Items in the Output Window
|
||||||
|
/// (taken from env\msenv\core\findutil.cpp ParseLocation function)
|
||||||
|
///
|
||||||
|
/// v:\dir\file.ext (loc) : msg
|
||||||
|
/// \\server\share\dir\file.ext(loc):msg
|
||||||
|
/// url
|
||||||
|
///
|
||||||
|
/// loc:
|
||||||
|
/// (line)
|
||||||
|
/// (line-line)
|
||||||
|
/// (line,col)
|
||||||
|
/// (line,col-col)
|
||||||
|
/// (line,col,len)
|
||||||
|
/// (line,col,line,col)
|
||||||
|
///
|
||||||
|
/// DevDiv Build Process
|
||||||
|
/// (taken from tools\devdiv2.def)
|
||||||
|
///
|
||||||
|
/// To echo warnings and errors to the build console, the
|
||||||
|
/// "description block" must be recognized by build. To do this,
|
||||||
|
/// add a $(ECHO_COMPILING_COMMAND) or $(ECHO_PROCESSING_COMMAND)
|
||||||
|
/// to the first line of the description block, e.g.
|
||||||
|
///
|
||||||
|
/// $(ECHO_COMPILING_CMD) Resgen_$<
|
||||||
|
///
|
||||||
|
/// Errors must have the format:
|
||||||
|
///
|
||||||
|
/// <text> : error [num]: <msg>
|
||||||
|
///
|
||||||
|
/// Warnings must have the format:
|
||||||
|
///
|
||||||
|
/// <text> : warning [num]: <msg>
|
||||||
|
/// </remarks>
|
||||||
|
/// <owner>JomoF</owner>
|
||||||
|
internal static class CanonicalError
|
||||||
|
{
|
||||||
|
// Defines the main pattern for matching messages.
|
||||||
|
private static readonly Regex s_originCategoryCodeTextExpression = new Regex
|
||||||
|
(
|
||||||
|
// Beginning of line and any amount of whitespace.
|
||||||
|
@"^\s*"
|
||||||
|
// Match a [optional project number prefix 'ddd>'], single letter + colon + remaining filename, or
|
||||||
|
// string with no colon followed by a colon.
|
||||||
|
+ @"(((?<ORIGIN>(((\d+>)?[a-zA-Z]?:[^:]*)|([^:]*))):)"
|
||||||
|
// Origin may also be empty. In this case there's no trailing colon.
|
||||||
|
+ "|())"
|
||||||
|
// Match the empty string or a string without a colon that ends with a space
|
||||||
|
+ "(?<SUBCATEGORY>(()|([^:]*? )))"
|
||||||
|
// Match 'error' or 'warning'.
|
||||||
|
+ @"(?<CATEGORY>(error|warning))"
|
||||||
|
// Match anything starting with a space that's not a colon/space, followed by a colon.
|
||||||
|
// Error code is optional in which case "error"/"warning" can be followed immediately by a colon.
|
||||||
|
+ @"( \s*(?<CODE>[^: ]*))?\s*:"
|
||||||
|
// Whatever's left on this line, including colons.
|
||||||
|
+ "(?<TEXT>.*)$",
|
||||||
|
RegexOptions.IgnoreCase
|
||||||
|
);
|
||||||
|
|
||||||
|
// Matches and extracts filename and location from an 'origin' element.
|
||||||
|
private static readonly Regex s_filenameLocationFromOrigin = new Regex
|
||||||
|
(
|
||||||
|
"^" // Beginning of line
|
||||||
|
+ @"(\d+>)?" // Optional ddd> project number prefix
|
||||||
|
+ "(?<FILENAME>.*)" // Match anything.
|
||||||
|
+ @"\(" // Find a parenthesis.
|
||||||
|
+ @"(?<LOCATION>[\,,0-9,-]*)" // Match any combination of numbers and ',' and '-'
|
||||||
|
+ @"\)\s*" // Find the closing paren then any amount of spaces.
|
||||||
|
+ "$", // End-of-line
|
||||||
|
RegexOptions.IgnoreCase
|
||||||
|
);
|
||||||
|
|
||||||
|
// Matches location that is a simple number.
|
||||||
|
private static readonly Regex s_lineFromLocation = new Regex // Example: line
|
||||||
|
(
|
||||||
|
"^" // Beginning of line
|
||||||
|
+ "(?<LINE>[0-9]*)" // Match any number.
|
||||||
|
+ "$", // End-of-line
|
||||||
|
RegexOptions.IgnoreCase
|
||||||
|
);
|
||||||
|
|
||||||
|
// Matches location that is a range of lines.
|
||||||
|
private static readonly Regex s_lineLineFromLocation = new Regex // Example: line-line
|
||||||
|
(
|
||||||
|
"^" // Beginning of line
|
||||||
|
+ "(?<LINE>[0-9]*)" // Match any number.
|
||||||
|
+ "-" // Dash
|
||||||
|
+ "(?<ENDLINE>[0-9]*)" // Match any number.
|
||||||
|
+ "$", // End-of-line
|
||||||
|
RegexOptions.IgnoreCase
|
||||||
|
);
|
||||||
|
|
||||||
|
// Matches location that is a line and column
|
||||||
|
private static readonly Regex s_lineColFromLocation = new Regex // Example: line,col
|
||||||
|
(
|
||||||
|
"^" // Beginning of line
|
||||||
|
+ "(?<LINE>[0-9]*)" // Match any number.
|
||||||
|
+ "," // Comma
|
||||||
|
+ "(?<COLUMN>[0-9]*)" // Match any number.
|
||||||
|
+ "$", // End-of-line
|
||||||
|
RegexOptions.IgnoreCase
|
||||||
|
);
|
||||||
|
|
||||||
|
// Matches location that is a line and column-range
|
||||||
|
private static readonly Regex s_lineColColFromLocation = new Regex // Example: line,col-col
|
||||||
|
(
|
||||||
|
"^" // Beginning of line
|
||||||
|
+ "(?<LINE>[0-9]*)" // Match any number.
|
||||||
|
+ "," // Comma
|
||||||
|
+ "(?<COLUMN>[0-9]*)" // Match any number.
|
||||||
|
+ "-" // Dash
|
||||||
|
+ "(?<ENDCOLUMN>[0-9]*)" // Match any number.
|
||||||
|
+ "$", // End-of-line
|
||||||
|
RegexOptions.IgnoreCase
|
||||||
|
);
|
||||||
|
|
||||||
|
// Matches location that is line,col,line,col
|
||||||
|
private static readonly Regex s_lineColLineColFromLocation = new Regex // Example: line,col,line,col
|
||||||
|
(
|
||||||
|
"^" // Beginning of line
|
||||||
|
+ "(?<LINE>[0-9]*)" // Match any number.
|
||||||
|
+ "," // Comma
|
||||||
|
+ "(?<COLUMN>[0-9]*)" // Match any number.
|
||||||
|
+ "," // Dash
|
||||||
|
+ "(?<ENDLINE>[0-9]*)" // Match any number.
|
||||||
|
+ "," // Dash
|
||||||
|
+ "(?<ENDCOLUMN>[0-9]*)" // Match any number.
|
||||||
|
+ "$", // End-of-line
|
||||||
|
RegexOptions.IgnoreCase
|
||||||
|
);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents the parts of a decomposed canonical message.
|
||||||
|
/// </summary>
|
||||||
|
/// <owner>JomoF</owner>
|
||||||
|
internal sealed class Parts
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Defines the error category\severity level.
|
||||||
|
/// </summary>
|
||||||
|
internal enum Category
|
||||||
|
{
|
||||||
|
Warning,
|
||||||
|
Error
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Value used for unspecified line and column numbers, which are 1-relative.
|
||||||
|
/// </summary>
|
||||||
|
internal const int numberNotSpecified = 0;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="Parts"/> class.
|
||||||
|
/// </summary>
|
||||||
|
internal Parts()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Name of the file or tool (not localized)
|
||||||
|
/// </summary>
|
||||||
|
internal string origin;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The line number.
|
||||||
|
/// </summary>
|
||||||
|
internal int line = Parts.numberNotSpecified;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The column number.
|
||||||
|
/// </summary>
|
||||||
|
internal int column = Parts.numberNotSpecified;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The ending line number.
|
||||||
|
/// </summary>
|
||||||
|
internal int endLine = Parts.numberNotSpecified;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The ending column number.
|
||||||
|
/// </summary>
|
||||||
|
internal int endColumn = Parts.numberNotSpecified;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The category/severity level
|
||||||
|
/// </summary>
|
||||||
|
internal Category category;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The sub category (localized)
|
||||||
|
/// </summary>
|
||||||
|
internal string subcategory;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The error code (not localized)
|
||||||
|
/// </summary>
|
||||||
|
internal string code;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The error message text (localized)
|
||||||
|
/// </summary>
|
||||||
|
internal string text;
|
||||||
|
|
||||||
|
#if NEVER
|
||||||
|
internal new string ToString()
|
||||||
|
{
|
||||||
|
return String.Format
|
||||||
|
(
|
||||||
|
"Origin='{0}'\n"
|
||||||
|
+"Filename='{1}'\n"
|
||||||
|
+"Line='{2}'\n"
|
||||||
|
+"Column='{3}'\n"
|
||||||
|
+"EndLine='{4}'\n"
|
||||||
|
+"EndColumn='{5}'\n"
|
||||||
|
+"Category='{6}'\n"
|
||||||
|
+"Subcategory='{7}'\n"
|
||||||
|
+"Text='{8}'\n"
|
||||||
|
, origin, line, column, endLine, endColumn, category.ToString(), subcategory, code, text
|
||||||
|
);
|
||||||
|
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A small custom int conversion method that treats invalid entries as missing (0). This is done to work around tools
|
||||||
|
/// that don't fully conform to the canonical message format - we still want to salvage what we can from the message.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="value"></param>
|
||||||
|
/// <returns>'value' converted to int or 0 if it can't be parsed or is negative</returns>
|
||||||
|
/// <owner>LukaszG</owner>
|
||||||
|
private static int ConvertToIntWithDefault(string value)
|
||||||
|
{
|
||||||
|
int result;
|
||||||
|
bool success = int.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture, out result);
|
||||||
|
|
||||||
|
if (!success || (result < 0))
|
||||||
|
{
|
||||||
|
result = CanonicalError.Parts.numberNotSpecified;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Decompose an error or warning message into constituent parts. If the message isn't in the canonical form, return null.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>This method is thread-safe, because the Regex class is thread-safe (per MSDN).</remarks>
|
||||||
|
/// <owner>JomoF</owner>
|
||||||
|
/// <param name="message"></param>
|
||||||
|
/// <returns>Decomposed canonical message, or null.</returns>
|
||||||
|
internal static Parts Parse(string message)
|
||||||
|
{
|
||||||
|
// An unusually long string causes pathologically slow Regex back-tracking.
|
||||||
|
// To avoid that, only scan the first 400 characters. That's enough for
|
||||||
|
// the longest possible prefix: MAX_PATH, plus a huge subcategory string, and an error location.
|
||||||
|
// After the regex is done, we can append the overflow.
|
||||||
|
string messageOverflow = String.Empty;
|
||||||
|
if (message.Length > 400)
|
||||||
|
{
|
||||||
|
messageOverflow = message.Substring(400);
|
||||||
|
message = message.Substring(0, 400);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If a tool has a large amount of output that isn't an error or warning (eg., "dir /s %hugetree%")
|
||||||
|
// the regex below is slow. It's faster to pre-scan for "warning" and "error"
|
||||||
|
// and bail out if neither are present.
|
||||||
|
if (message.IndexOf("warning", StringComparison.OrdinalIgnoreCase) == -1 &&
|
||||||
|
message.IndexOf("error", StringComparison.OrdinalIgnoreCase) == -1)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
Parts parsedMessage = new Parts();
|
||||||
|
|
||||||
|
// First, split the message into three parts--Origin, Category, Code, Text.
|
||||||
|
// Example,
|
||||||
|
// Main.cs(17,20):Command line warning CS0168: The variable 'foo' is declared but never used
|
||||||
|
// -------------- ------------ ------- ------ ----------------------------------------------
|
||||||
|
// Origin SubCategory Cat. Code Text
|
||||||
|
//
|
||||||
|
// To accommodate absolute filenames in Origin, tolerate a colon in the second position
|
||||||
|
// as long as its preceded by a letter.
|
||||||
|
//
|
||||||
|
// Localization Note:
|
||||||
|
// Even in foreign-language versions of tools, the category field needs to be in English.
|
||||||
|
// Also, if origin is a tool name, then that needs to be in English.
|
||||||
|
//
|
||||||
|
// Here's an example from the Japanese version of CL.EXE:
|
||||||
|
// cl : ???? ??? warning D4024 : ?????????? 'AssemblyInfo.cs' ?????????????????? ???????????
|
||||||
|
//
|
||||||
|
// Here's an example from the Japanese version of LINK.EXE:
|
||||||
|
// AssemblyInfo.cpp : fatal error LNK1106: ???????????? ??????????????: 0x6580 ??????????
|
||||||
|
//
|
||||||
|
Match match = s_originCategoryCodeTextExpression.Match(message);
|
||||||
|
|
||||||
|
if (!match.Success)
|
||||||
|
{
|
||||||
|
// If no match here, then this message is not an error or warning.
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
string origin = match.Groups["ORIGIN"].Value.Trim();
|
||||||
|
string category = match.Groups["CATEGORY"].Value.Trim();
|
||||||
|
parsedMessage.code = match.Groups["CODE"].Value.Trim();
|
||||||
|
parsedMessage.text = (match.Groups["TEXT"].Value + messageOverflow).Trim();
|
||||||
|
parsedMessage.subcategory = match.Groups["SUBCATEGORY"].Value.Trim();
|
||||||
|
|
||||||
|
// Next, see if category is something that is recognized.
|
||||||
|
if (0 == String.Compare(category, "error", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
parsedMessage.category = Parts.Category.Error;
|
||||||
|
}
|
||||||
|
else if (0 == String.Compare(category, "warning", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
parsedMessage.category = Parts.Category.Warning;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Not an error\warning message.
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Origin is not a simple file, but it still could be of the form,
|
||||||
|
// foo.cpp(location)
|
||||||
|
match = s_filenameLocationFromOrigin.Match(origin);
|
||||||
|
|
||||||
|
if (match.Success)
|
||||||
|
{
|
||||||
|
// The origin is in the form,
|
||||||
|
// foo.cpp(location)
|
||||||
|
// Assume the filename exists, but don't verify it. What else could it be?
|
||||||
|
string location = match.Groups["LOCATION"].Value.Trim();
|
||||||
|
parsedMessage.origin = match.Groups["FILENAME"].Value.Trim();
|
||||||
|
|
||||||
|
// Now, take apart the location. It can be one of these:
|
||||||
|
// loc:
|
||||||
|
// (line)
|
||||||
|
// (line-line)
|
||||||
|
// (line,col)
|
||||||
|
// (line,col-col)
|
||||||
|
// (line,col,len)
|
||||||
|
// (line,col,line,col)
|
||||||
|
if (location.Length > 0)
|
||||||
|
{
|
||||||
|
match = s_lineFromLocation.Match(location);
|
||||||
|
if (match.Success)
|
||||||
|
{
|
||||||
|
parsedMessage.line = ConvertToIntWithDefault(match.Groups["LINE"].Value.Trim());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
match = s_lineLineFromLocation.Match(location);
|
||||||
|
if (match.Success)
|
||||||
|
{
|
||||||
|
parsedMessage.line = ConvertToIntWithDefault(match.Groups["LINE"].Value.Trim());
|
||||||
|
parsedMessage.endLine = ConvertToIntWithDefault(match.Groups["ENDLINE"].Value.Trim());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
match = s_lineColFromLocation.Match(location);
|
||||||
|
if (match.Success)
|
||||||
|
{
|
||||||
|
parsedMessage.line = ConvertToIntWithDefault(match.Groups["LINE"].Value.Trim());
|
||||||
|
parsedMessage.column = ConvertToIntWithDefault(match.Groups["COLUMN"].Value.Trim());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
match = s_lineColColFromLocation.Match(location);
|
||||||
|
if (match.Success)
|
||||||
|
{
|
||||||
|
parsedMessage.line = ConvertToIntWithDefault(match.Groups["LINE"].Value.Trim());
|
||||||
|
parsedMessage.column = ConvertToIntWithDefault(match.Groups["COLUMN"].Value.Trim());
|
||||||
|
parsedMessage.endColumn = ConvertToIntWithDefault(match.Groups["ENDCOLUMN"].Value.Trim());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
match = s_lineColLineColFromLocation.Match(location);
|
||||||
|
if (match.Success)
|
||||||
|
{
|
||||||
|
parsedMessage.line = ConvertToIntWithDefault(match.Groups["LINE"].Value.Trim());
|
||||||
|
parsedMessage.column = ConvertToIntWithDefault(match.Groups["COLUMN"].Value.Trim());
|
||||||
|
parsedMessage.endLine = ConvertToIntWithDefault(match.Groups["ENDLINE"].Value.Trim());
|
||||||
|
parsedMessage.endColumn = ConvertToIntWithDefault(match.Groups["ENDCOLUMN"].Value.Trim());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// The origin does not fit the filename(location) pattern.
|
||||||
|
parsedMessage.origin = origin;
|
||||||
|
}
|
||||||
|
|
||||||
|
return parsedMessage;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
@ -77,36 +77,17 @@ namespace Microsoft.DotNet.Tools.Compiler
|
||||||
|
|
||||||
private static bool Compile(ProjectContext context, string configuration, string outputPath, bool buildProjectReferences)
|
private static bool Compile(ProjectContext context, string configuration, string outputPath, bool buildProjectReferences)
|
||||||
{
|
{
|
||||||
Reporter.Output.WriteLine($"Building {context.RootProject.Identity.Name.Yellow()} for {context.TargetFramework.DotNetFrameworkName.Yellow()}");
|
Reporter.Output.WriteLine($"Compiling {context.RootProject.Identity.Name.Yellow()} for {context.TargetFramework.DotNetFrameworkName.Yellow()}");
|
||||||
|
|
||||||
// Create the library exporter
|
// Create the library exporter
|
||||||
var exporter = context.CreateExporter(configuration);
|
var exporter = context.CreateExporter(configuration);
|
||||||
|
|
||||||
|
var diagnostics = new List<DiagnosticMessage>();
|
||||||
|
|
||||||
bool success = true;
|
bool success = true;
|
||||||
|
|
||||||
// Print out dependency diagnostics
|
// Collect dependency diagnostics
|
||||||
foreach (var diag in context.LibraryManager.GetAllDiagnostics())
|
diagnostics.AddRange(context.LibraryManager.GetAllDiagnostics());
|
||||||
{
|
|
||||||
switch (diag.Severity)
|
|
||||||
{
|
|
||||||
case DiagnosticMessageSeverity.Info:
|
|
||||||
Reporter.Error.WriteLine(diag.FormattedMessage);
|
|
||||||
break;
|
|
||||||
case DiagnosticMessageSeverity.Warning:
|
|
||||||
Reporter.Error.WriteLine(diag.FormattedMessage.Yellow().Bold());
|
|
||||||
break;
|
|
||||||
case DiagnosticMessageSeverity.Error:
|
|
||||||
success = false;
|
|
||||||
Reporter.Error.WriteLine(diag.FormattedMessage.Red().Bold());
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If there were dependency errors don't bother compiling
|
|
||||||
if (!success)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Gather exports for the project
|
// Gather exports for the project
|
||||||
var dependencies = exporter.GetCompilationDependencies().ToList();
|
var dependencies = exporter.GetCompilationDependencies().ToList();
|
||||||
|
@ -137,7 +118,6 @@ namespace Microsoft.DotNet.Tools.Compiler
|
||||||
|
|
||||||
if (compileResult.ExitCode != 0)
|
if (compileResult.ExitCode != 0)
|
||||||
{
|
{
|
||||||
Console.Error.WriteLine($"Failed to compile dependency: {projectDependency.Identity.Name.Red()}");
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -223,19 +203,68 @@ namespace Microsoft.DotNet.Tools.Compiler
|
||||||
File.WriteAllLines(rsp, compilerArgs);
|
File.WriteAllLines(rsp, compilerArgs);
|
||||||
|
|
||||||
var result = Command.Create($"dotnet-compile-{compiler}", $"\"{rsp}\"")
|
var result = Command.Create($"dotnet-compile-{compiler}", $"\"{rsp}\"")
|
||||||
.ForwardStdErr()
|
.OnErrorLine(line =>
|
||||||
.ForwardStdOut()
|
{
|
||||||
|
var diagnostic = ParseDiagnostic(context.ProjectDirectory, line);
|
||||||
|
if (diagnostic != null)
|
||||||
|
{
|
||||||
|
diagnostics.Add(diagnostic);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Console.Error.WriteLine(line);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.OnOutputLine(line =>
|
||||||
|
{
|
||||||
|
var diagnostic = ParseDiagnostic(context.ProjectDirectory, line);
|
||||||
|
|
||||||
|
if (diagnostic != null)
|
||||||
|
{
|
||||||
|
diagnostics.Add(diagnostic);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Console.Out.WriteLine(line);
|
||||||
|
}
|
||||||
|
})
|
||||||
.RunAsync()
|
.RunAsync()
|
||||||
.GetAwaiter()
|
.GetAwaiter()
|
||||||
.GetResult();
|
.GetResult();
|
||||||
|
|
||||||
if (result.ExitCode == 0)
|
foreach (var diag in diagnostics)
|
||||||
{
|
{
|
||||||
Reporter.Output.WriteLine($"Compiled {context.ProjectFile.Name} successfully!".Green());
|
success &= diag.Severity != DiagnosticMessageSeverity.Error;
|
||||||
return true;
|
PrintDiagnostic(diag);
|
||||||
|
}
|
||||||
|
|
||||||
|
success &= result.ExitCode == 0;
|
||||||
|
|
||||||
|
PrintSummary(diagnostics);
|
||||||
|
|
||||||
|
return success;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void PrintSummary(List<DiagnosticMessage> diagnostics)
|
||||||
|
{
|
||||||
|
Reporter.Output.Writer.WriteLine();
|
||||||
|
|
||||||
|
var errorCount = diagnostics.Count(d => d.Severity == DiagnosticMessageSeverity.Error);
|
||||||
|
var warningCount = diagnostics.Count(d => d.Severity == DiagnosticMessageSeverity.Warning);
|
||||||
|
|
||||||
|
if (errorCount > 0)
|
||||||
|
{
|
||||||
|
Reporter.Output.WriteLine("Compilation failed.".Red());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Reporter.Output.WriteLine("Compilation succeeded.".Green());
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
Reporter.Output.WriteLine($" {warningCount} Warning(s)");
|
||||||
|
Reporter.Output.WriteLine($" {errorCount} Error(s)");
|
||||||
|
|
||||||
|
Reporter.Output.Writer.WriteLine();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool AddResources(Project project, List<string> compilerArgs, string intermediateOutputPath)
|
private static bool AddResources(Project project, List<string> compilerArgs, string intermediateOutputPath)
|
||||||
|
@ -323,6 +352,47 @@ namespace Microsoft.DotNet.Tools.Compiler
|
||||||
outputs.Add(project);
|
outputs.Add(project);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static DiagnosticMessage ParseDiagnostic(string projectRootPath, string line)
|
||||||
|
{
|
||||||
|
var error = CanonicalError.Parse(line);
|
||||||
|
|
||||||
|
if (error != null)
|
||||||
|
{
|
||||||
|
var severity = error.category == CanonicalError.Parts.Category.Error ?
|
||||||
|
DiagnosticMessageSeverity.Error : DiagnosticMessageSeverity.Warning;
|
||||||
|
|
||||||
|
return new DiagnosticMessage(
|
||||||
|
error.code,
|
||||||
|
error.text,
|
||||||
|
Path.IsPathRooted(error.origin) ? line : projectRootPath + Path.DirectorySeparatorChar + line,
|
||||||
|
Path.Combine(projectRootPath, error.origin),
|
||||||
|
severity,
|
||||||
|
error.line,
|
||||||
|
error.column,
|
||||||
|
error.endColumn,
|
||||||
|
error.endLine,
|
||||||
|
source: null);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void PrintDiagnostic(DiagnosticMessage diag)
|
||||||
|
{
|
||||||
|
switch (diag.Severity)
|
||||||
|
{
|
||||||
|
case DiagnosticMessageSeverity.Info:
|
||||||
|
Reporter.Error.WriteLine(diag.FormattedMessage);
|
||||||
|
break;
|
||||||
|
case DiagnosticMessageSeverity.Warning:
|
||||||
|
Reporter.Error.WriteLine(diag.FormattedMessage.Yellow().Bold());
|
||||||
|
break;
|
||||||
|
case DiagnosticMessageSeverity.Error:
|
||||||
|
Reporter.Error.WriteLine(diag.FormattedMessage.Red().Bold());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static void ApplyCompilationOptions(CompilerOptions compilationOptions, List<string> cscArgs, bool bootstrappingWithMono)
|
private static void ApplyCompilationOptions(CompilerOptions compilationOptions, List<string> cscArgs, bool bootstrappingWithMono)
|
||||||
{
|
{
|
||||||
// TODO: Move compilation arguments into the compiler itself
|
// TODO: Move compilation arguments into the compiler itself
|
||||||
|
|
|
@ -1,8 +1,6 @@
|
||||||
// Copyright (c) .NET Foundation. All rights reserved.
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
using Microsoft.Extensions.JsonParser.Sources;
|
|
||||||
|
|
||||||
namespace Microsoft.Extensions.ProjectModel
|
namespace Microsoft.Extensions.ProjectModel
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
|
@ -1,12 +1,9 @@
|
||||||
// Copyright (c) .NET Foundation. All rights reserved.
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
// 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.Collections.Generic;
|
||||||
using System.IO;
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Microsoft.Extensions.ProjectModel.Graph;
|
using Microsoft.Extensions.ProjectModel.Graph;
|
||||||
using NuGet.Versioning;
|
|
||||||
|
|
||||||
namespace Microsoft.Extensions.ProjectModel.Resolution
|
namespace Microsoft.Extensions.ProjectModel.Resolution
|
||||||
{
|
{
|
||||||
|
@ -47,7 +44,7 @@ namespace Microsoft.Extensions.ProjectModel.Resolution
|
||||||
foreach (var range in library.RequestedRanges)
|
foreach (var range in library.RequestedRanges)
|
||||||
{
|
{
|
||||||
errorCode = ErrorCodes.NU1001;
|
errorCode = ErrorCodes.NU1001;
|
||||||
message = $"The dependency {range.Name} {range.VersionRange} could not be resolved.";
|
message = $"The dependency {FormatLibraryRange(range)} could not be resolved.";
|
||||||
|
|
||||||
AddDiagnostics(messages, library, message, errorCode);
|
AddDiagnostics(messages, library, message, errorCode);
|
||||||
}
|
}
|
||||||
|
@ -107,6 +104,16 @@ namespace Microsoft.Extensions.ProjectModel.Resolution
|
||||||
return messages;
|
return messages;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static string FormatLibraryRange(LibraryRange range)
|
||||||
|
{
|
||||||
|
if (range.VersionRange == null)
|
||||||
|
{
|
||||||
|
return range.Name;
|
||||||
|
}
|
||||||
|
|
||||||
|
return range.Name + " " + range.VersionRange;
|
||||||
|
}
|
||||||
|
|
||||||
private void AddDiagnostics(List<DiagnosticMessage> messages, LibraryDescription library, string message, string errorCode)
|
private void AddDiagnostics(List<DiagnosticMessage> messages, LibraryDescription library, string message, string errorCode)
|
||||||
{
|
{
|
||||||
// A (in project.json) -> B (unresolved) (not in project.json)
|
// A (in project.json) -> B (unresolved) (not in project.json)
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue