提交 49f6947d 编写于 作者: A Andy Gocke

Add the C# and VB MSBuild tasks to Roslyn

This moves the existing tasks into the source repository for Roslyn, as Roslyn
owns the C# and VB MSBuild tasks.
上级 9de3519f
using System;
using System.Globalization;
using System.Text.RegularExpressions;
namespace Microsoft.CodeAnalysis.BuildTask
{
/// <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_$&lt;
///
/// Errors must have the format:
///
/// &lt;text&gt; : error [num]: &lt;msg&gt;
///
/// Warnings must have the format:
///
/// &lt;text&gt; : warning [num]: &lt;msg&gt;
/// </remarks>
/// <owner>JomoF</owner>
internal static class CanonicalError
{
// Defines the main pattern for matching messages.
private static Regex 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 Regex 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 Regex 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 Regex 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 Regex 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 Regex 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 Regex 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 accomodate 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 = 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 = 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 = lineFromLocation.Match(location);
if (match.Success)
{
parsedMessage.line = ConvertToIntWithDefault(match.Groups["LINE"].Value.Trim());
}
else
{
match = lineLineFromLocation.Match(location);
if (match.Success)
{
parsedMessage.line = ConvertToIntWithDefault(match.Groups["LINE"].Value.Trim());
parsedMessage.endLine = ConvertToIntWithDefault(match.Groups["ENDLINE"].Value.Trim());
}
else
{
match = lineColFromLocation.Match(location);
if (match.Success)
{
parsedMessage.line = ConvertToIntWithDefault(match.Groups["LINE"].Value.Trim());
parsedMessage.column = ConvertToIntWithDefault(match.Groups["COLUMN"].Value.Trim());
}
else
{
match = 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 = 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;
}
}
}
using System;
using System.Diagnostics;
using System.Globalization;
using System.Text;
using Microsoft.Build.Framework;
using Microsoft.Build.Utilities;
namespace Microsoft.CodeAnalysis.BuildTask
{
/// <summary>
/// CommandLineBuilder derived class for specialized logic specific to MSBuild tasks
/// </summary>
public class CommandLineBuilderExtension : CommandLineBuilder
{
/// <summary>
/// Set a boolean switch iff its value exists and its value is 'true'.
/// </summary>
internal void AppendWhenTrue
(
string switchName,
PropertyDictionary bag,
string parameterName
)
{
object obj = bag[parameterName];
// If the switch isn't set, don't add it to the command line.
if (obj != null)
{
bool value = (bool)obj;
if (value)
{
AppendSwitch(switchName);
}
}
}
/// <summary>
/// Set a boolean switch only if its value exists.
/// </summary>
internal void AppendPlusOrMinusSwitch
(
string switchName,
PropertyDictionary bag,
string parameterName
)
{
object obj = bag[parameterName];
// If the switch isn't set, don't add it to the command line.
if (obj != null)
{
bool value = (bool)obj;
// Do not quote - or + as they are part of the switch
AppendSwitchUnquotedIfNotNull(switchName, (value ? "+" : "-"));
}
}
/// <summary>
/// Set a switch if its value exists by choosing from the input choices
/// </summary>
internal void AppendByChoiceSwitch
(
string switchName,
PropertyDictionary bag,
string parameterName,
string choice1,
string choice2
)
{
object obj = bag[parameterName];
// If the switch isn't set, don't add it to the command line.
if (obj != null)
{
bool value = (bool)obj;
AppendSwitchUnquotedIfNotNull(switchName, (value ? choice1 : choice2));
}
}
/// <summary>
/// Set an integer switch only if its value exists.
/// </summary>
internal void AppendSwitchWithInteger
(
string switchName,
PropertyDictionary bag,
string parameterName
)
{
object obj = bag[parameterName];
// If the switch isn't set, don't add it to the command line.
if (obj != null)
{
int value = (int)obj;
AppendSwitchIfNotNull(switchName, value.ToString(CultureInfo.InvariantCulture));
}
}
/// <summary>
/// Adds an aliased switch, used for ResGen:
/// /reference:Foo=System.Xml.dll
/// </summary>
internal void AppendSwitchAliased(string switchName, string alias, string parameter)
{
AppendSwitchUnquotedIfNotNull(switchName, alias + "=");
AppendTextWithQuoting(parameter);
}
/// <summary>
/// Adds a nested switch, used by SGen.exe. For example:
/// /compiler:"/keyfile:\"c:\some folder\myfile.snk\""
/// </summary>
internal void AppendNestedSwitch(string outerSwitchName, string innerSwitchName, string parameter)
{
string quotedParameter = GetQuotedText(parameter);
AppendSwitchIfNotNull(outerSwitchName, innerSwitchName + quotedParameter);
}
/// <summary>
/// Returns a quoted string appropriate for appending to a command line.
/// </summary>
/// <remarks>
/// Escapes any double quotes in the string.
/// </remarks>
protected string GetQuotedText(string unquotedText)
{
StringBuilder quotedText = new StringBuilder();
AppendQuotedTextToBuffer(quotedText, unquotedText);
return quotedText.ToString();
}
/// <summary>
/// Appends a command-line switch that takes a compound string parameter. The parameter is built up from the item-spec and
/// the specified attributes. The switch is appended as many times as there are parameters given.
/// </summary>
internal void AppendSwitchIfNotNull
(
string switchName,
ITaskItem[] parameters,
string[] attributes
)
{
AppendSwitchIfNotNull(switchName, parameters, attributes, null /* treatAsFlag */);
}
/// <summary>
/// Append a switch if 'parameter' is not null.
/// Split on the characters provided.
/// </summary>
internal void AppendSwitchWithSplitting(string switchName, string parameter, string delimiter, params char[] splitOn)
{
if (parameter != null)
{
string[] splits = parameter.Split(splitOn, /* omitEmptyEntries */ StringSplitOptions.RemoveEmptyEntries);
string[] splitAndTrimmed = new string[splits.Length];
for (int i = 0; i < splits.Length; ++i)
{
splitAndTrimmed[i] = splits[i].Trim();
}
AppendSwitchIfNotNull(switchName, splitAndTrimmed, delimiter);
}
}
/// <summary>
/// Returns true if the parameter is empty in spirits,
/// even if it contains the seperators and white space only
/// Split on the characters provided.
/// </summary>
internal static bool IsParameterEmpty(string parameter, params char[] splitOn)
{
if (parameter != null)
{
string[] splits = parameter.Split(splitOn, /* omitEmptyEntries */ StringSplitOptions.RemoveEmptyEntries);
for (int i = 0; i < splits.Length; ++i)
{
if (!String.IsNullOrEmpty(splits[i].Trim()))
{
return false;
}
}
}
return true;
}
/// <summary>
/// Designed to handle the /link and /embed swithes:
///
/// /embed[resource]:&lt;filename>[,&lt;name>[,Private]]
/// /link[resource]:&lt;filename>[,&lt;name>[,Private]]
///
/// Where the last flag--Private--is either present or not present
/// depending on whether the ITaskItem has a Private="True" attribue.
/// </summary>
internal void AppendSwitchIfNotNull
(
string switchName,
ITaskItem[] parameters,
string[] metadataNames,
bool[] treatAsFlags // May be null. In this case no metadata are treated as flags.
)
{
Debug.Assert(treatAsFlags == null
|| (metadataNames.Length == treatAsFlags.Length),
"metadataNames and treatAsFlags should have the same length.");
if (parameters != null)
{
foreach (ITaskItem parameter in parameters)
{
AppendSwitchIfNotNull(switchName, parameter.ItemSpec);
if (metadataNames != null)
{
for (int i = 0; i < metadataNames.Length; ++i)
{
string metadataValue = parameter.GetMetadata(metadataNames[i]);
if ((metadataValue != null) && (metadataValue.Length > 0))
{
// Treat attribute as a boolean flag?
if (treatAsFlags == null || treatAsFlags[i] == false)
{
// Not a boolean flag.
CommandLine.Append(',');
AppendTextWithQuoting(metadataValue);
}
else
{
// A boolean flag.
bool flagSet = false;
flagSet = Utilities.TryConvertItemMetadataToBool(parameter, metadataNames[i]);
if (flagSet)
{
CommandLine.Append(',');
AppendTextWithQuoting(metadataNames[i]);
}
}
}
else
{
if (treatAsFlags == null || treatAsFlags[i] == false)
{
// If the caller of this method asked us to add metadata
// A, B, and C, and metadata A doesn't exist on the item,
// then it doesn't make sense to check for B and C. Because
// since these metadata are just being appended on the
// command-line switch with comma-separation, you can't pass
// in the B metadata unless you've also passed in the A
// metadata. Otherwise the tool's command-line parser will
// get totally confused.
// This only applies to non-flag attributes.
break;
}
}
}
}
}
}
}
}
}
using System;
using System.Text;
using Microsoft.Build.Framework;
using Microsoft.Build.Tasks.Hosting;
using Microsoft.Build.Utilities;
namespace Microsoft.CodeAnalysis.BuildTask
{
/// <summary>
/// This class defines the "Csc" XMake task, which enables building assemblies from C#
/// source files by invoking the C# compiler. This is the new Roslyn XMake task,
/// meaning that the code is compiled by using the Roslyn compiler server, rather
/// than csc.exe. The two should be functionally identical, but the compiler server
/// should be significantly faster with larger projects and have a smaller memory
/// footprint.
/// </summary>
public class Csc : ManagedCompiler
{
bool useHostCompilerIfAvailable = false;
#region Properties
// Please keep these alphabetized. These are the parameters specific to Csc. The
// ones shared between Vbc and Csc are defined in ManagedCompiler.cs, which is
// the base class.
public bool AllowUnsafeBlocks
{
set { _store["AllowUnsafeBlocks"] = value; }
get { return _store.GetOrDefault("AllowUnsafeBlocks", false); }
}
public string ApplicationConfiguration
{
set { _store["ApplicationConfiguration"] = value; }
get { return (string)_store["ApplicationConfiguration"]; }
}
public string BaseAddress
{
set { _store["BaseAddress"] = value; }
get { return (string)_store["BaseAddress"]; }
}
public bool CheckForOverflowUnderflow
{
set { _store["CheckForOverflowUnderflow"] = value; }
get { return _store.GetOrDefault("CheckForOverflowUnderflow", false); }
}
public string DocumentationFile
{
set { _store["DocumentationFile"] = value; }
get { return (string)_store["DocumentationFile"]; }
}
public string DisabledWarnings
{
set { _store["DisabledWarnings"] = value; }
get { return (string)_store["DisabledWarnings"]; }
}
public bool ErrorEndLocation
{
set { _store["ErrorEndLocation"] = value; }
get { return _store.GetOrDefault("ErrorEndLocation", false); }
}
public string ErrorReport
{
set { _store["ErrorReport"] = value; }
get { return (string)_store["ErrorReport"]; }
}
public bool GenerateFullPaths
{
set { _store["GenerateFullPaths"] = value; }
get { return _store.GetOrDefault("GenerateFullPaths", false); }
}
public string LangVersion
{
set { _store["LangVersion"] = value; }
get { return (string)_store["LangVersion"]; }
}
public string ModuleAssemblyName
{
set { _store["ModuleAssemblyName"] = value; }
get { return (string)_store["ModuleAssemblyName"]; }
}
public bool NoStandardLib
{
set { _store["NoStandardLib"] = value; }
get { return _store.GetOrDefault("NoStandardLib", false); }
}
public string PdbFile
{
set { _store["PdbFile"] = value; }
get { return (string)_store["PdbFile"]; }
}
/// <summary>
/// Name of the language passed to "/preferreduilang" compiler option.
/// </summary>
/// <remarks>
/// If set to null, "/preferreduilang" option is omitted, and csc.exe uses its default setting.
/// Otherwise, the value is passed to "/preferreduilang" as is.
/// </remarks>
public string PreferredUILang
{
set { _store["PreferredUILang"] = value; }
get { return (string)_store["PreferredUILang"]; }
}
public string VsSessionGuid
{
set { _store["VsSessionGuid"] = value; }
get { return (string)_store["VsSessionGuid"]; }
}
public bool UseHostCompilerIfAvailable
{
set { this.useHostCompilerIfAvailable = value; }
get { return this.useHostCompilerIfAvailable; }
}
public int WarningLevel
{
set { _store["WarningLevel"] = value; }
get { return _store.GetOrDefault("WarningLevel", 4); }
}
public string WarningsAsErrors
{
set { _store["WarningsAsErrors"] = value; }
get { return (string)_store["WarningsAsErrors"]; }
}
public string WarningsNotAsErrors
{
set { _store["WarningsNotAsErrors"] = value; }
get { return (string)_store["WarningsNotAsErrors"]; }
}
#endregion
#region Tool Members
/// <summary>
/// Return the name of the tool to execute.
/// </summary>
override protected string ToolName
{
get
{
return "csc2.exe";
}
}
/// <summary>
/// Return the path to the tool to execute.
/// </summary>
override protected string GenerateFullPathToTool()
{
string pathToTool = ToolLocationHelper.GetPathToBuildToolsFile(ToolName, ToolLocationHelper.CurrentToolsVersion);
if (null == pathToTool)
{
pathToTool = ToolLocationHelper.GetPathToDotNetFrameworkFile(ToolName, TargetDotNetFrameworkVersion.VersionLatest);
if (null == pathToTool)
{
Log.LogErrorWithCodeFromResources("General.FrameworksFileNotFound", ToolName, ToolLocationHelper.GetDotNetFrameworkVersionFolderPrefix(TargetDotNetFrameworkVersion.VersionLatest));
}
}
return pathToTool;
}
/// <summary>
/// Fills the provided CommandLineBuilderExtension with those switches and other information that can go into a response file.
/// </summary>
override protected internal void AddResponseFileCommands(CommandLineBuilderExtension commandLine)
{
commandLine.AppendSwitchIfNotNull("/lib:", this.AdditionalLibPaths, ",");
commandLine.AppendPlusOrMinusSwitch("/unsafe", this._store, "AllowUnsafeBlocks");
commandLine.AppendPlusOrMinusSwitch("/checked", this._store, "CheckForOverflowUnderflow");
commandLine.AppendSwitchWithSplitting("/nowarn:", this.DisabledWarnings, ",", ';', ',');
commandLine.AppendWhenTrue("/fullpaths", this._store, "GenerateFullPaths");
commandLine.AppendSwitchIfNotNull("/langversion:", this.LangVersion);
commandLine.AppendSwitchIfNotNull("/moduleassemblyname:", this.ModuleAssemblyName);
commandLine.AppendSwitchIfNotNull("/pdb:", this.PdbFile);
commandLine.AppendPlusOrMinusSwitch("/nostdlib", this._store, "NoStandardLib");
commandLine.AppendSwitchIfNotNull("/platform:", this.PlatformWith32BitPreference);
commandLine.AppendSwitchIfNotNull("/errorreport:", this.ErrorReport);
commandLine.AppendSwitchWithInteger("/warn:", this._store, "WarningLevel");
commandLine.AppendSwitchIfNotNull("/doc:", this.DocumentationFile);
commandLine.AppendSwitchIfNotNull("/baseaddress:", this.BaseAddress);
commandLine.AppendSwitchUnquotedIfNotNull("/define:", this.GetDefineConstantsSwitch(this.DefineConstants));
commandLine.AppendSwitchIfNotNull("/win32res:", this.Win32Resource);
commandLine.AppendSwitchIfNotNull("/main:", this.MainEntryPoint);
commandLine.AppendSwitchIfNotNull("/appconfig:", this.ApplicationConfiguration);
commandLine.AppendWhenTrue("/errorendlocation", this._store, "ErrorEndLocation");
commandLine.AppendSwitchIfNotNull("/preferreduilang:", this.PreferredUILang);
commandLine.AppendPlusOrMinusSwitch("/highentropyva", this._store, "HighEntropyVA");
// If not design time build and the globalSessionGuid property was set then add a -globalsessionguid:<guid>
bool designTime = false;
if (this.HostObject != null)
{
var csHost = this.HostObject as ICscHostObject;
designTime = csHost.IsDesignTime();
}
if (!designTime)
{
if (!string.IsNullOrWhiteSpace(this.VsSessionGuid))
{
commandLine.AppendSwitchIfNotNull("/sqmsessionguid:", this.VsSessionGuid);
}
}
this.AddReferencesToCommandLine(commandLine);
base.AddResponseFileCommands(commandLine);
// This should come after the "TreatWarningsAsErrors" flag is processed (in managedcompiler.cs).
// Because if TreatWarningsAsErrors=false, then we'll have a /warnaserror- on the command-line,
// and then any specific warnings that should be treated as errors should be specified with
// /warnaserror+:<list> after the /warnaserror- switch. The order of the switches on the command-line
// does matter.
//
// Note that
// /warnaserror+
// is just shorthand for:
// /warnaserror+:<all possible warnings>
//
// Similarly,
// /warnaserror-
// is just shorthand for:
// /warnaserror-:<all possible warnings>
commandLine.AppendSwitchWithSplitting("/warnaserror+:", this.WarningsAsErrors, ",", ';', ',');
commandLine.AppendSwitchWithSplitting("/warnaserror-:", this.WarningsNotAsErrors, ",", ';', ',');
// It's a good idea for the response file to be the very last switch passed, just
// from a predictability perspective. It also solves the problem that a dogfooder
// ran into, which is described in an email thread attached to bug VSWhidbey 146883.
// See also bugs 177762 and 118307 for additional bugs related to response file position.
if (this.ResponseFiles != null)
{
foreach (ITaskItem response in this.ResponseFiles)
{
commandLine.AppendSwitchIfNotNull("@", response.ItemSpec);
}
}
}
#endregion
/// <summary>
/// The C# compiler (starting with Whidbey) supports assembly aliasing for references.
/// See spec at http://devdiv/spectool/Documents/Whidbey/VCSharp/Design%20Time/M3%20DCRs/DCR%20Assembly%20aliases.doc.
/// This method handles the necessary work of looking at the "Aliases" attribute on
/// the incoming "References" items, and making sure to generate the correct
/// command-line on csc.exe. The syntax for aliasing a reference is:
/// csc.exe /reference:Foo=System.Xml.dll
///
/// The "Aliases" attribute on the "References" items is actually a comma-separated
/// list of aliases, and if any of the aliases specified is the string "global",
/// then we add that reference to the command-line without an alias.
/// </summary>
/// <param name="commandLine"></param>
/// <owner>RGoel</owner>
void AddReferencesToCommandLine
(
CommandLineBuilderExtension commandLine
)
{
// If there were no references passed in, don't add any /reference: switches
// on the command-line.
if ((this.References == null) || (this.References.Length == 0))
{
return;
}
// Loop through all the references passed in. We'll be adding separate
// /reference: switches for each reference, and in some cases even multiple
// /reference: switches per reference.
foreach (ITaskItem reference in this.References)
{
// See if there was an "Alias" attribute on the reference.
string aliasString = reference.GetMetadata("Aliases");
string switchName = "/reference:";
bool embed = Utilities.TryConvertItemMetadataToBool(reference,
"EmbedInteropTypes");
if (embed == true)
{
switchName = "/link:";
}
if ((aliasString == null) || (aliasString.Length == 0))
{
// If there was no "Alias" attribute, just add this as a global reference.
commandLine.AppendSwitchIfNotNull(switchName, reference.ItemSpec);
}
else
{
// If there was an "Alias" attribute, it contains a comma-separated list
// of aliases to use for this reference. For each one of those aliases,
// we're going to add a separate /reference: switch to the csc.exe
// command-line
string[] aliases = aliasString.Split(',');
foreach (string alias in aliases)
{
// Trim whitespace.
string trimmedAlias = alias.Trim();
if (alias.Length == 0)
{
continue;
}
// The alias should be a valid C# identifier. Therefore it cannot
// contain comma, space, semicolon, or double-quote. Let's check for
// the existence of those characters right here, and bail immediately
// if any are present. There are a whole bunch of other characters
// that are not allowed in a C# identifier, but we'll just let csc.exe
// error out on those. The ones we're checking for here are the ones
// that could seriously screw up the command-line parsing or could
// allow parameter injection.
if (trimmedAlias.IndexOfAny(new char[] { ',', ' ', ';', '"' }) != -1)
{
throw Utilities.GetLocalizedArgumentException(
ErrorString.Csc_AssemblyAliasContainsIllegalCharacters,
reference.ItemSpec,
trimmedAlias);
}
// The alias called "global" is special. It means that we don't
// give it an alias on the command-line.
if (String.Compare("global", trimmedAlias, StringComparison.OrdinalIgnoreCase) == 0)
{
commandLine.AppendSwitchIfNotNull(switchName, reference.ItemSpec);
}
else
{
// We have a valid (and explicit) alias for this reference. Add
// it to the command-line using the syntax:
// /reference:Foo=System.Xml.dll
commandLine.AppendSwitchAliased(switchName, trimmedAlias, reference.ItemSpec);
}
}
}
}
}
/// <summary>
/// Old VS projects had some pretty messed-up looking values for the
/// "DefineConstants" property. It worked fine in the IDE, because it
/// effectively munged up the string so that it ended up being valid for
/// the compiler. We do the equivalent munging here now.
///
/// Basically, we take the incoming string, and split it on comma/semicolon/space.
/// Then we look at the resulting list of strings, and remove any that are
/// illegal identifiers, and pass the remaining ones through to the compiler.
///
/// Note that CSharp does support assigning a value to the constants ... in
/// other words, a constant is either defined or not defined ... it can't have
/// an actual value.
/// </summary>
internal string GetDefineConstantsSwitch(string originalDefineConstants)
{
if (originalDefineConstants == null)
{
return null;
}
StringBuilder finalDefineConstants = new StringBuilder();
// Split the incoming string on comma/semicolon/space.
string[] allIdentifiers = originalDefineConstants.Split(new char[] { ',', ';', ' ' });
// Loop through all the parts, and for the ones that are legal C# identifiers,
// add them to the outgoing string.
foreach (string singleIdentifier in allIdentifiers)
{
if (CSharp.SyntaxFacts.IsValidIdentifier(singleIdentifier))
{
// Separate them with a semicolon if there's something already in
// the outgoing string.
if (finalDefineConstants.Length > 0)
{
finalDefineConstants.Append(";");
}
finalDefineConstants.Append(singleIdentifier);
}
else if (singleIdentifier.Length > 0)
{
Log.LogWarningWithCodeFromResources("Csc.InvalidParameterWarning", "/define:", singleIdentifier);
}
}
if (finalDefineConstants.Length > 0)
{
return finalDefineConstants.ToString();
}
else
{
// We wouldn't want to pass in an empty /define: switch on the csc.exe command-line.
return null;
}
}
}
}
\ No newline at end of file
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// Runtime Version:4.0.30319.0
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
namespace Microsoft.CodeAnalysis.BuildTask {
using System;
/// <summary>
/// A strongly-typed resource class, for looking up localized strings, etc.
/// </summary>
// This class was auto-generated by the StronglyTypedResourceBuilder
// class via a tool like ResGen or Visual Studio.
// To add or remove a member, edit your .ResX file then rerun ResGen
// with the /str option, or rebuild your VS project.
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
internal class ErrorString {
private static global::System.Resources.ResourceManager resourceMan;
private static global::System.Globalization.CultureInfo resourceCulture;
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
internal ErrorString() {
}
/// <summary>
/// Returns the cached ResourceManager instance used by this class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Resources.ResourceManager ResourceManager {
get {
if (object.ReferenceEquals(resourceMan, null)) {
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Microsoft.CodeAnalysis.BuildTask.ErrorString", typeof(ErrorString).Assembly);
resourceMan = temp;
}
return resourceMan;
}
}
/// <summary>
/// Overrides the current thread's CurrentUICulture property for all
/// resource lookups using this strongly typed resource class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Globalization.CultureInfo Culture {
get {
return resourceCulture;
}
set {
resourceCulture = value;
}
}
/// <summary>
/// Looks up a localized string similar to MSB3053: The assembly alias &quot;{1}&quot; on reference &quot;{0}&quot; contains illegal characters..
/// </summary>
internal static string Csc_AssemblyAliasContainsIllegalCharacters {
get {
return ResourceManager.GetString("Csc_AssemblyAliasContainsIllegalCharacters", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to MSB3051: The parameter to the compiler is invalid. {0}.
/// </summary>
internal static string Csc_InvalidParameter {
get {
return ResourceManager.GetString("Csc_InvalidParameter", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to MSB3052: The parameter to the compiler is invalid, &apos;{0}{1}&apos; will be ignored..
/// </summary>
internal static string Csc_InvalidParameterWarning {
get {
return ResourceManager.GetString("Csc_InvalidParameterWarning", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to The string &quot;{0}&quot; cannot be converted to a boolean (true/false) value..
/// </summary>
internal static string General_CannotConvertStringToBool {
get {
return ResourceManager.GetString("General_CannotConvertStringToBool", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Item &quot;{0}&quot; has attribute &quot;{1}&quot; with value &quot;{2}&quot; that could not be converted to &quot;{3}&quot;..
/// </summary>
internal static string General_InvalidAttributeMetadata {
get {
return ResourceManager.GetString("General_InvalidAttributeMetadata", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to &quot;{1}&quot; is an invalid value for the &quot;{0}&quot; parameter..
/// </summary>
internal static string Vbc_ParameterHasInvalidValue {
get {
return ResourceManager.GetString("Vbc_ParameterHasInvalidValue", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to MSB3402: There was an error creating the pdb file &quot;{0}&quot;. {1}.
/// </summary>
internal static string Vbc_RenamePDB {
get {
return ResourceManager.GetString("Vbc_RenamePDB", resourceCulture);
}
}
}
}
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="Csc_AssemblyAliasContainsIllegalCharacters" xml:space="preserve">
<value>MSB3053: The assembly alias "{1}" on reference "{0}" contains illegal characters.</value>
<comment>{StrBegin="MSB3053: "}</comment>
</data>
<data name="Csc_InvalidParameter" xml:space="preserve">
<value>MSB3051: The parameter to the compiler is invalid. {0}</value>
<comment>{StrBegin="MSB3051: "}</comment>
</data>
<data name="Csc_InvalidParameterWarning" xml:space="preserve">
<value>MSB3052: The parameter to the compiler is invalid, '{0}{1}' will be ignored.</value>
<comment>{StrBegin="MSB3052: "}</comment>
</data>
<data name="General_CannotConvertStringToBool" xml:space="preserve">
<value>The string "{0}" cannot be converted to a boolean (true/false) value.</value>
</data>
<data name="General_InvalidAttributeMetadata" xml:space="preserve">
<value>Item "{0}" has attribute "{1}" with value "{2}" that could not be converted to "{3}".</value>
</data>
<data name="Vbc_ParameterHasInvalidValue" xml:space="preserve">
<value>"{1}" is an invalid value for the "{0}" parameter.</value>
</data>
<data name="Vbc_RenamePDB" xml:space="preserve">
<value>MSB3402: There was an error creating the pdb file "{0}". {1}</value>
<comment>{StrBegin="MSB3402: "}</comment>
</data>
</root>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -->
<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ImportGroup Label="Settings">
<Import Project="..\..\..\Tools\Microsoft.CodeAnalysis.Toolset.Open\Targets\VSL.Settings.targets" />
<Import Project="..\..\..\..\build\VSL.Settings.Closed.targets" />
</ImportGroup>
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{D874349C-8BB3-4BDC-8535-2D52CCCA1198}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>Microsoft.CodeAnalysis.BuildTask</RootNamespace>
<AssemblyName>Microsoft.Build.Tasks.Roslyn</AssemblyName>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' " />
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' " />
<ItemGroup>
<Reference Include="Microsoft.Build.Framework, Version=$(VisualStudioReferenceAssemblyVersion), Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" />
<Reference Include="Microsoft.Build.Tasks.$(MSBuildAssemblyNameFragment), Version=$(VisualStudioReferenceAssemblyVersion), Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" />
<Reference Include="Microsoft.Build.Utilities.$(MSBuildAssemblyNameFragment), Version=$(VisualStudioReferenceAssemblyVersion), Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" />
<Reference Include="System" />
</ItemGroup>
<ItemGroup>
<None Include="packages.config" />
</ItemGroup>
<ItemGroup>
<Compile Include="..\..\CSharp\Portable\Parser\CharacterInfo.cs">
<Link>CharacterInfo.cs</Link>
</Compile>
<Compile Include="CanonicalError.cs" />
<Compile Include="CommandLineBuilderExtension.cs" />
<Compile Include="Csc.cs" />
<Compile Include="ErrorString.Designer.cs">
<AutoGen>True</AutoGen>
<DesignTime>True</DesignTime>
<DependentUpon>ErrorString.resx</DependentUpon>
</Compile>
<Compile Include="ManagedCompiler.cs" />
<Compile Include="PropertyDictionary.cs" />
<Compile Include="Utilities.cs" />
<Compile Include="Vbc.cs" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="ErrorString.resx">
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>ErrorString.Designer.cs</LastGenOutput>
</EmbeddedResource>
</ItemGroup>
<ImportGroup Label="Targets">
<Import Project="..\..\..\Tools\Microsoft.CodeAnalysis.Toolset.Open\Targets\VSL.Imports.targets" />
<Import Project="..\..\..\..\build\VSL.Imports.Closed.targets" />
</ImportGroup>
</Project>
\ No newline at end of file
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Resources;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Build.Framework;
using Microsoft.Build.Shared;
using Microsoft.Build.Tasks;
using Microsoft.Build.Utilities;
namespace Microsoft.CodeAnalysis.BuildTask
{
/// <summary>
/// This class defines all of the common stuff that is shared between the Vbc and Csc tasks.
/// This class is not instantiatable as a Task just by itself.
/// </summary>
public abstract class ManagedCompiler : ToolTask
{
internal readonly PropertyDictionary _store = new PropertyDictionary();
#region Properties
// Please keep these alphabetized.
public string[] AdditionalLibPaths
{
set { _store["AdditionalLibPaths"] = value; }
get { return (string[])_store["AdditionalLibPaths"]; }
}
public string[] AddModules
{
set { _store["AddModules"] = value; }
get { return (string[])_store["AddModules"]; }
}
public ITaskItem[] AdditionalFiles
{
set { _store["AdditionalFiles"] = value; }
get { return (ITaskItem[])_store["AdditionalFiles"]; }
}
public ITaskItem[] Analyzers
{
set { _store["Analyzers"] = value; }
get { return (ITaskItem[])_store["Analyzers"]; }
}
// We do not support BugReport because it always requires user interaction,
// which will cause a hang.
public string CodeAnalysisRuleSet
{
set { _store["CodeAnalysisRuleSet"] = value; }
get { return (string)_store["CodeAnalysisRuleSet"]; }
}
public int CodePage
{
set { _store["CodePage"] = value; }
get { return _store.GetOrDefault("CodePage", 0); }
}
public string DebugType
{
set { _store["DebugType"] = value; }
get { return (string)_store["DebugType"]; }
}
public string DefineConstants
{
set { _store["DefineConstants"] = value; }
get { return (string)_store["DefineConstants"]; }
}
public bool DelaySign
{
set { _store["DelaySign"] = value; }
get { return _store.GetOrDefault("DelaySign", false); }
}
public bool EmitDebugInformation
{
set { _store["EmitDebugInformation"] = value; }
get { return _store.GetOrDefault("EmitDebugInformation", false); }
}
public int FileAlignment
{
set { _store["FileAlignment"] = value; }
get { return _store.GetOrDefault("FileAlignment", 0); }
}
public bool HighEntropyVA
{
set { _store["HighEntropyVA"] = value; }
get { return _store.GetOrDefault("HighEntropyVA", false); }
}
public string KeyContainer
{
set { _store["KeyContainer"] = value; }
get { return (string)_store["KeyContainer"]; }
}
public string KeyFile
{
set { _store["KeyFile"] = value; }
get { return (string)_store["KeyFile"]; }
}
public ITaskItem[] LinkResources
{
set { _store["LinkResources"] = value; }
get { return (ITaskItem[])_store["LinkResources"]; }
}
public string MainEntryPoint
{
set { _store["MainEntryPoint"] = value; }
get { return (string)_store["MainEntryPoint"]; }
}
public bool NoConfig
{
set { _store["NoConfig"] = value; }
get { return _store.GetOrDefault("NoConfig", false); }
}
public bool NoLogo
{
set { _store["NoLogo"] = value; }
get { return _store.GetOrDefault("NoLogo", false); }
}
public bool NoWin32Manifest
{
set { _store["NoWin32Manifest"] = value; }
get { return _store.GetOrDefault("NoWin32Manifest", false); }
}
public bool Optimize
{
set { _store["Optimize"] = value; }
get { return _store.GetOrDefault("Optimize", false); }
}
[Output]
public ITaskItem OutputAssembly
{
set { _store["OutputAssembly"] = value; }
get { return (ITaskItem)_store["OutputAssembly"]; }
}
public string Platform
{
set { _store["Platform"] = value; }
get { return (string)_store["Platform"]; }
}
public bool Prefer32Bit
{
set { _store["Prefer32Bit"] = value; }
get { return _store.GetOrDefault("Prefer32Bit", false); }
}
public ITaskItem[] References
{
set { _store["References"] = value; }
get { return (ITaskItem[])_store["References"]; }
}
public ITaskItem[] Resources
{
set { _store["Resources"] = value; }
get { return (ITaskItem[])_store["Resources"]; }
}
public ITaskItem[] ResponseFiles
{
set { _store["ResponseFiles"] = value; }
get { return (ITaskItem[])_store["ResponseFiles"]; }
}
public ITaskItem[] Sources
{
set
{
if (UsedCommandLineTool)
{
NormalizePaths(value);
}
_store["Sources"] = value;
}
get { return (ITaskItem[])_store["Sources"]; }
}
public string SubsystemVersion
{
set { _store["SubsystemVersion"] = value; }
get { return (string)_store["SubsystemVersion"]; }
}
public string TargetType
{
set { _store["TargetType"] = value.ToLower(CultureInfo.InvariantCulture); }
get { return (string)_store["TargetType"]; }
}
public bool TreatWarningsAsErrors
{
set { _store["TreatWarningsAsErrors"] = value; }
get { return _store.GetOrDefault("TreatWarningsAsErrors", false); }
}
public bool Utf8Output
{
set { _store["Utf8Output"] = value; }
get { return _store.GetOrDefault("Utf8Output", false); }
}
public string Win32Icon
{
set { _store["Win32Icon"] = value; }
get { return (string)_store["Win32Icon"]; }
}
public string Win32Manifest
{
set { _store["Win32Manifest"] = value; }
get { return (string)_store["Win32Manifest"]; }
}
public string Win32Resource
{
set { _store["Win32Resource"] = value; }
get { return (string)_store["Win32Resource"]; }
}
// Map explicit platform of "AnyCPU" or the default platform (null or ""), since it is commonly understood in the
// managed build process to be equivalent to "AnyCPU", to platform "AnyCPU32BitPreferred" if the Prefer32Bit
// property is set.
internal string PlatformWith32BitPreference
{
get
{
string platform = this.Platform;
if ((String.IsNullOrEmpty(platform) || platform.Equals("anycpu", StringComparison.OrdinalIgnoreCase)) && this.Prefer32Bit)
{
platform = "anycpu32bitpreferred";
}
return platform;
}
}
/// <summary>
/// Overridable property specifying the encoding of the captured task standard output stream
/// </summary>
protected override Encoding StandardOutputEncoding
{
get
{
return (Utf8Output) ? Encoding.UTF8 : base.StandardOutputEncoding;
}
}
#endregion
protected override string GenerateCommandLineCommands()
{
CommandLineBuilderExtension commandLineBuilder = new CommandLineBuilderExtension();
AddCommandLineCommands(commandLineBuilder);
return commandLineBuilder.ToString();
}
/// <summary>
/// Fills the provided CommandLineBuilderExtension with those switches and other information that can't go into a response file and
/// must go directly onto the command line.
/// </summary>
protected internal virtual void AddCommandLineCommands(CommandLineBuilderExtension commandLine)
{
commandLine.AppendWhenTrue("/noconfig", this._store, "NoConfig");
}
/// <summary>
/// Fills the provided CommandLineBuilderExtension with those switches and other information that can go into a response file.
/// </summary>
protected internal virtual void AddResponseFileCommands(CommandLineBuilderExtension commandLine)
{
// If outputAssembly is not specified, then an "/out: <name>" option won't be added to
// overwrite the one resulting from the OutputAssembly member of the CompilerParameters class.
// In that case, we should set the outputAssembly member based on the first source file.
if (
(OutputAssembly == null) &&
(Sources != null) &&
(Sources.Length > 0) &&
(this.ResponseFiles == null) // The response file may already have a /out: switch in it, so don't try to be smart here.
)
{
try
{
OutputAssembly = new TaskItem(Path.GetFileNameWithoutExtension(Sources[0].ItemSpec));
}
catch (ArgumentException e)
{
throw new ArgumentException(e.Message, "Sources");
}
if (String.Compare(TargetType, "library", StringComparison.OrdinalIgnoreCase) == 0)
{
OutputAssembly.ItemSpec += ".dll";
}
else if (String.Compare(TargetType, "module", StringComparison.OrdinalIgnoreCase) == 0)
{
OutputAssembly.ItemSpec += ".netmodule";
}
else
{
OutputAssembly.ItemSpec += ".exe";
}
}
commandLine.AppendSwitchIfNotNull("/addmodule:", this.AddModules, ",");
commandLine.AppendSwitchWithInteger("/codepage:", this._store, "CodePage");
ConfigureDebugProperties();
// The "DebugType" parameter should be processed after the "EmitDebugInformation" parameter
// because it's more specific. Order matters on the command-line, and the last one wins.
// /debug+ is just a shorthand for /debug:full. And /debug- is just a shorthand for /debug:none.
commandLine.AppendPlusOrMinusSwitch("/debug", this._store, "EmitDebugInformation");
commandLine.AppendSwitchIfNotNull("/debug:", this.DebugType);
commandLine.AppendPlusOrMinusSwitch("/delaysign", this._store, "DelaySign");
commandLine.AppendSwitchWithInteger("/filealign:", this._store, "FileAlignment");
commandLine.AppendSwitchIfNotNull("/keycontainer:", this.KeyContainer);
commandLine.AppendSwitchIfNotNull("/keyfile:", this.KeyFile);
// If the strings "LogicalName" or "Access" ever change, make sure to search/replace everywhere in vsproject.
commandLine.AppendSwitchIfNotNull("/linkresource:", this.LinkResources, new string[] { "LogicalName", "Access" });
commandLine.AppendWhenTrue("/nologo", this._store, "NoLogo");
commandLine.AppendWhenTrue("/nowin32manifest", this._store, "NoWin32Manifest");
commandLine.AppendPlusOrMinusSwitch("/optimize", this._store, "Optimize");
commandLine.AppendSwitchIfNotNull("/out:", this.OutputAssembly);
commandLine.AppendSwitchIfNotNull("/ruleset:", this.CodeAnalysisRuleSet);
commandLine.AppendSwitchIfNotNull("/subsystemversion:", this.SubsystemVersion);
// If the strings "LogicalName" or "Access" ever change, make sure to search/replace everywhere in vsproject.
commandLine.AppendSwitchIfNotNull("/resource:", this.Resources, new string[] { "LogicalName", "Access" });
commandLine.AppendSwitchIfNotNull("/target:", this.TargetType);
commandLine.AppendPlusOrMinusSwitch("/warnaserror", this._store, "TreatWarningsAsErrors");
commandLine.AppendWhenTrue("/utf8output", this._store, "Utf8Output");
commandLine.AppendSwitchIfNotNull("/win32icon:", this.Win32Icon);
commandLine.AppendSwitchIfNotNull("/win32manifest:", this.Win32Manifest);
// Append the analyzers.
this.AddAnalyzersToCommandLine(commandLine);
// Append additional files.
this.AddAdditionalFilesToCommandLine(commandLine);
// Append the sources.
commandLine.AppendFileNamesIfNotNull(Sources, " ");
}
/// <summary>
/// Adds a "/analyzer:" switch to the command line for each provided analyzer.
/// </summary>
private void AddAnalyzersToCommandLine(CommandLineBuilderExtension commandLine)
{
// If there were no analyzers passed in, don't add any /analyzer: switches
// on the command-line.
if ((this.Analyzers == null) || (this.Analyzers.Length == 0))
{
return;
}
foreach (ITaskItem analyzer in this.Analyzers)
{
commandLine.AppendSwitchIfNotNull("/analyzer:", analyzer.ItemSpec);
}
}
/// <summary>
/// Adds a "/analyzer:" switch to the command line for each provided analyzer.
/// </summary>
private void AddAdditionalFilesToCommandLine(CommandLineBuilderExtension commandLine)
{
// If there were no additional files passed in, don't add any /additionalfile: switches
// on the command-line.
if ((this.AdditionalFiles == null) || (this.AdditionalFiles.Length == 0))
{
return;
}
foreach (ITaskItem additionalFile in this.AdditionalFiles)
{
commandLine.AppendSwitchIfNotNull("/additionalfile:", additionalFile.ItemSpec);
}
}
/// <summary>
/// Configure the debug switches which will be placed on the compiler commandline.
/// The matrix of debug type and symbol inputs and the desired results is as follows:
///
/// Debug Symbols DebugType Desired Resilts
/// True Full /debug+ /debug:full
/// True PdbOnly /debug+ /debug:PdbOnly
/// True None /debug-
/// True Blank /debug+
/// False Full /debug- /debug:full
/// False PdbOnly /debug- /debug:PdbOnly
/// False None /debug-
/// False Blank /debug-
/// Blank Full /debug:full
/// Blank PdbOnly /debug:PdbOnly
/// Blank None /debug-
/// Debug: Blank Blank /debug+ //Microsof.common.targets will set this
/// Release: Blank Blank "Nothing for either switch"
///
/// The logic is as follows:
/// If debugtype is none set debugtype to empty and debugSymbols to false
/// If debugType is blank use the debugsymbols "as is"
/// If debug type is set, use its value and the debugsymbols value "as is"
/// </summary>
private void ConfigureDebugProperties()
{
// If debug type is set we need to take some action depending on the value. If debugtype is not set
// We don't need to modify the EmitDebugInformation switch as its value will be used as is.
if (_store["DebugType"] != null)
{
// If debugtype is none then only show debug- else use the debug type and the debugsymbols as is.
if (string.Compare((string)_store["DebugType"], "none", StringComparison.OrdinalIgnoreCase) == 0)
{
_store["DebugType"] = null;
_store["EmitDebugInformation"] = false;
}
}
}
/// <summary>
/// Validate parameters, log errors and warnings and return true if
/// Execute should proceed.
/// </summary>
protected override bool ValidateParameters()
{
return ListHasNoDuplicateItems(this.Resources, "Resources", "LogicalName") && ListHasNoDuplicateItems(this.Sources, "Sources");
}
/// <summary>
/// Returns true if the provided item list contains duplicate items, false otherwise.
/// </summary>
protected bool ListHasNoDuplicateItems(ITaskItem[] itemList, string parameterName)
{
return ListHasNoDuplicateItems(itemList, parameterName, null);
}
/// <summary>
/// Returns true if the provided item list contains duplicate items, false otherwise.
/// </summary>
/// <param name="itemList"></param>
/// <param name="disambiguatingMetadataName">Optional name of metadata that may legitimately disambiguate items. May be null.</param>
/// <param name="parameterName"></param>
private bool ListHasNoDuplicateItems(ITaskItem[] itemList, string parameterName, string disambiguatingMetadataName)
{
if (itemList == null || itemList.Length == 0)
{
return true;
}
Hashtable alreadySeen = new Hashtable(StringComparer.OrdinalIgnoreCase);
foreach (ITaskItem item in itemList)
{
string key;
string disambiguatingMetadataValue = null;
if (disambiguatingMetadataName != null)
{
disambiguatingMetadataValue = item.GetMetadata(disambiguatingMetadataName);
}
if (disambiguatingMetadataName == null || String.IsNullOrEmpty(disambiguatingMetadataValue))
{
key = item.ItemSpec;
}
else
{
key = item.ItemSpec + ":" + disambiguatingMetadataValue;
}
if (alreadySeen.ContainsKey(key))
{
if (disambiguatingMetadataName == null || String.IsNullOrEmpty(disambiguatingMetadataValue))
{
Log.LogErrorWithCodeFromResources("General.DuplicateItemsNotSupported", item.ItemSpec, parameterName);
}
else
{
Log.LogErrorWithCodeFromResources("General.DuplicateItemsNotSupportedWithMetadata", item.ItemSpec, parameterName, disambiguatingMetadataValue, disambiguatingMetadataName);
}
return false;
}
else
{
alreadySeen[key] = String.Empty;
}
}
return true;
}
/// <summary>
/// Allows tool to handle the return code.
/// This method will only be called with non-zero exitCode.
/// </summary>
protected override bool HandleTaskExecutionErrors()
{
// For managed compilers, the compiler should emit the appropriate
// error messages before returning a non-zero exit code, so we don't
// normally need to emit any additional messages now.
//
// If somehow the compiler DID return a non-zero exit code and didn't log an error, we'd like to log that exit code.
// We can only do this for the command line compiler: if the inproc compiler was used,
// we can't tell what if anything it logged as it logs directly to Visual Studio's output window.
//
if (!Log.HasLoggedErrors && UsedCommandLineTool)
{
// This will log a message "MSB3093: The command exited with code {0}."
base.HandleTaskExecutionErrors();
}
return false;
}
/// <summary>
/// Takes a list of files and returns the normalized locations of these files
/// </summary>
private void NormalizePaths(ITaskItem[] taskItems)
{
foreach (var item in taskItems)
{
item.ItemSpec = Utilities.GetFullPathNoThrow(item.ItemSpec);
}
}
/// <summary>
/// Whether the command line compiler was invoked, instead
/// of the host object compiler.
/// </summary>
protected bool UsedCommandLineTool
{
get;
set;
}
private bool hostCompilerSupportsAllParameters;
protected bool HostCompilerSupportsAllParameters
{
get { return this.hostCompilerSupportsAllParameters; }
set { this.hostCompilerSupportsAllParameters = value; }
}
/// <summary>
/// Checks the bool result from calling one of the methods on the host compiler object to
/// set one of the parameters. If it returned false, that means the host object doesn't
/// support a particular parameter or variation on a parameter. So we log a comment,
/// and set our state so we know not to call the host object to do the actual compilation.
/// </summary>
/// <owner>RGoel</owner>
protected void CheckHostObjectSupport
(
string parameterName,
bool resultFromHostObjectSetOperation
)
{
if (!resultFromHostObjectSetOperation)
{
Log.LogMessageFromResources(MessageImportance.Normal, "General.ParameterUnsupportedOnHostCompiler", parameterName);
this.hostCompilerSupportsAllParameters = false;
}
}
/// <summary>
/// Checks to see whether all of the passed-in references exist on disk before we launch the compiler.
/// </summary>
/// <owner>RGoel</owner>
protected bool CheckAllReferencesExistOnDisk()
{
if (null == this.References)
{
// No references
return true;
}
bool success = true;
foreach (ITaskItem reference in this.References)
{
if (!File.Exists(reference.ItemSpec))
{
success = false;
Log.LogErrorWithCodeFromResources("General.ReferenceDoesNotExist", reference.ItemSpec);
}
}
return success;
}
/// <summary>
/// The IDE and command line compilers unfortunately differ in how win32
/// manifests are specified. In particular, the command line compiler offers a
/// "/nowin32manifest" switch, while the IDE compiler does not offer analagous
/// functionality. If this switch is omitted from the command line and no win32
/// manifest is specified, the compiler will include a default win32 manifest
/// named "default.win32manifest" found in the same directory as the compiler
/// executable. Again, the IDE compiler does not offer analagous support.
///
/// We'd like to imitate the command line compiler's behavior in the IDE, but
/// it isn't aware of the default file, so we must compute the path to it if
/// noDefaultWin32Manifest is false and no win32Manifest was provided by the
/// project.
///
/// This method will only be called during the initialization of the host object,
/// which is only used during IDE builds.
/// </summary>
/// <param name="noDefaultWin32Manifest"></param>
/// <param name="win32Manifest"></param>
/// <returns>the path to the win32 manifest to provide to the host object</returns>
internal string GetWin32ManifestSwitch
(
bool noDefaultWin32Manifest,
string win32Manifest
)
{
if (!noDefaultWin32Manifest)
{
if (String.IsNullOrEmpty(win32Manifest) && String.IsNullOrEmpty(this.Win32Resource))
{
// We only want to consider the default.win32manifest if this is an executable
if (!String.Equals(TargetType, "library", StringComparison.OrdinalIgnoreCase)
&& !String.Equals(TargetType, "module", StringComparison.OrdinalIgnoreCase))
{
// We need to compute the path to the default win32 manifest
string pathToDefaultManifest = ToolLocationHelper.GetPathToDotNetFrameworkFile
(
"default.win32manifest",
TargetDotNetFrameworkVersion.VersionLatest
);
if (null == pathToDefaultManifest)
{
// This is rather unlikely, and the inproc compiler seems to log an error anyway.
// So just a message is fine.
Log.LogMessageFromResources
(
"General.ExpectedFileMissing",
"default.win32manifest"
);
}
return pathToDefaultManifest;
}
}
}
return win32Manifest;
}
}
}
\ No newline at end of file
using System.Collections.Generic;
namespace Microsoft.CodeAnalysis.BuildTask
{
internal class PropertyDictionary : Dictionary<string, object>
{
public T GetOrDefault<T>(string name, T @default)
{
object value;
if (this.TryGetValue(name, out value))
{
return (T)value;
}
return @default;
}
public new object this[string name]
{
get
{
object value;
return this.TryGetValue(name, out value)
? value : null;
}
set { this[name] = value; }
}
}
}
using System;
using System.Globalization;
using System.IO;
using System.Security;
using Microsoft.Build.Framework;
namespace Microsoft.CodeAnalysis.BuildTask
{
/// <summary>
/// General utilities.
/// </summary>
internal static class Utilities
{
/// <summary>
/// Convert a task item metadata to bool. Throw an exception if the string is badly formed and can't
/// be converted.
///
/// If the metadata is not found, then set metadataFound to false and then return false.
/// </summary>
/// <param name="item">The item that contains the metadata.</param>
/// <param name="itemMetadataName">The name of the metadata.</param>
/// <returns>The resulting boolean value.</returns>
internal static bool TryConvertItemMetadataToBool(ITaskItem item, string itemMetadataName)
{
string metadataValue = item.GetMetadata(itemMetadataName);
if (metadataValue == null || metadataValue.Length == 0)
{
return false;
}
try
{
return ConvertStringToBool(metadataValue);
}
catch (System.ArgumentException e)
{
throw Utilities.GetLocalizedArgumentException(
e,
ErrorString.General_InvalidAttributeMetadata,
item.ItemSpec, itemMetadataName, metadataValue, "bool");
}
}
/// <summary>
/// Converts a string to a bool. We consider "true/false", "on/off", and
/// "yes/no" to be valid boolean representations in the XML.
/// </summary>
/// <param name="parameterValue">The string to convert.</param>
/// <returns>Boolean true or false, corresponding to the string.</returns>
internal static bool ConvertStringToBool(string parameterValue)
{
if (ValidBooleanTrue(parameterValue))
{
return true;
}
else if (ValidBooleanFalse(parameterValue))
{
return false;
}
else
{
// Unsupported boolean representation.
throw Utilities.GetLocalizedArgumentException(
ErrorString.General_CannotConvertStringToBool,
parameterValue);
}
}
/// <summary>
/// Returns true if the string can be successfully converted to a bool,
/// such as "on" or "yes"
/// </summary>
internal static bool CanConvertStringToBool(string parameterValue) =>
ValidBooleanTrue(parameterValue) || ValidBooleanFalse(parameterValue);
/// <summary>
/// Returns true if the string represents a valid MSBuild boolean true value,
/// such as "on", "!false", "yes"
/// </summary>
private static bool ValidBooleanTrue(string parameterValue) =>
String.Compare(parameterValue, "true", StringComparison.OrdinalIgnoreCase) == 0 ||
String.Compare(parameterValue, "on", StringComparison.OrdinalIgnoreCase) == 0 ||
String.Compare(parameterValue, "yes", StringComparison.OrdinalIgnoreCase) == 0 ||
String.Compare(parameterValue, "!false", StringComparison.OrdinalIgnoreCase) == 0 ||
String.Compare(parameterValue, "!off", StringComparison.OrdinalIgnoreCase) == 0 ||
String.Compare(parameterValue, "!no", StringComparison.OrdinalIgnoreCase) == 0;
/// <summary>
/// Returns true if the string represents a valid MSBuild boolean false value,
/// such as "!on" "off" "no" "!true"
/// </summary>
private static bool ValidBooleanFalse(string parameterValue) =>
String.Compare(parameterValue, "false", StringComparison.OrdinalIgnoreCase) == 0 ||
String.Compare(parameterValue, "off", StringComparison.OrdinalIgnoreCase) == 0 ||
String.Compare(parameterValue, "no", StringComparison.OrdinalIgnoreCase) == 0 ||
String.Compare(parameterValue, "!true", StringComparison.OrdinalIgnoreCase) == 0 ||
String.Compare(parameterValue, "!on", StringComparison.OrdinalIgnoreCase) == 0 ||
String.Compare(parameterValue, "!yes", StringComparison.OrdinalIgnoreCase) == 0;
internal static string GetFullPathNoThrow(string path)
{
try
{
path = Path.GetFullPath(path);
}
catch (Exception e) when (IsIoRelatedException(e)) { }
return path;
}
internal static void DeleteNoThrow(string path)
{
try
{
File.Delete(path);
}
catch (Exception e) when (IsIoRelatedException(e)) { }
}
internal static bool IsIoRelatedException(Exception e) =>
e is UnauthorizedAccessException ||
e is NotSupportedException ||
(e is ArgumentException && !(e is ArgumentNullException)) ||
e is SecurityException ||
e is IOException;
internal static Exception GetLocalizedArgumentException(Exception e,
string errorString,
params object[] args)
{
return new ArgumentException(string.Format(CultureInfo.CurrentCulture, errorString, args), e);
}
internal static Exception GetLocalizedArgumentException(string errorString,
params object[] args)
{
return new ArgumentException(string.Format(CultureInfo.CurrentCulture, errorString, args));
}
}
}
using System;
using System.IO;
using System.Text;
using System.Collections.Generic;
using System.Globalization;
using Microsoft.Build.Framework;
using Microsoft.Build.Utilities;
using Microsoft.Build.Tasks.Hosting;
namespace Microsoft.CodeAnalysis.BuildTask
{
/// <summary>
/// This class defines the "Vbc" XMake task, which enables building assemblies from VB
/// source files by invoking the VB compiler. This is the new Roslyn XMake task,
/// meaning that the code is compiled by using the Roslyn compiler server, rather
/// than vbc.exe. The two should be functionally identical, but the compiler server
/// should be significantly faster with larger projects and have a smaller memory
/// footprint.
/// </summary>
public class Vbc : ManagedCompiler
{
bool useHostCompilerIfAvailable = false;
// The following 1 fields are used, set and re-set in LogEventsFromTextOutput()
/// <summary>
/// This stores the origional lines and error priority together in the order in which they were recieved.
/// </summary>
private Queue<VBError> vbErrorLines = new Queue<VBError>();
// Used when parsing vbc output to determine the column number of an error
private bool isDoneOutputtingErrorMessage = false;
private int numberOfLinesInErrorMessage = 0;
#region Properties
// Please keep these alphabetized. These are the parameters specific to Vbc. The
// ones shared between Vbc and Csc are defined in ManagedCompiler.cs, which is
// the base class.
public string BaseAddress
{
set { _store["BaseAddress"] = value; }
get { return (string)_store["BaseAddress"]; }
}
public string DisabledWarnings
{
set { _store["DisabledWarnings"] = value; }
get { return (string)_store["DisabledWarnings"]; }
}
public string DocumentationFile
{
set { _store["DocumentationFile"] = value; }
get { return (string)_store["DocumentationFile"]; }
}
public string ErrorReport
{
set { _store["ErrorReport"] = value; }
get { return (string)_store["ErrorReport"]; }
}
public bool GenerateDocumentation
{
set { _store["GenerateDocumentation"] = value; }
get { return _store.GetOrDefault("GenerateDocumentation", false); }
}
public ITaskItem[] Imports
{
set { _store["Imports"] = value; }
get { return (ITaskItem[])_store["Imports"]; }
}
public string LangVersion
{
set { _store["LangVersion"] = value; }
get { return (string)_store["LangVersion"]; }
}
public string ModuleAssemblyName
{
set { _store["ModuleAssemblyName"] = value; }
get { return (string)_store["ModuleAssemblyName"]; }
}
public bool NoStandardLib
{
set { _store["NoStandardLib"] = value; }
get { return _store.GetOrDefault("NoStandardLib", false); }
}
// This is not a documented switch. It prevents the automatic reference to Microsoft.VisualBasic.dll.
// The VB team believes the only scenario for this is when you are building that assembly itself.
// We have to support the switch here so that we can build the SDE and VB trees, which need to build this assembly.
// Although undocumented, it cannot be wrapped with #if BUILDING_DF_LKG because this would prevent dogfood builds
// within VS, which must use non-LKG msbuild bits.
public bool NoVBRuntimeReference
{
set { _store["NoVBRuntimeReference"] = value; }
get { return _store.GetOrDefault("NoVBRuntimeReference", false); }
}
public bool NoWarnings
{
set { _store["NoWarnings"] = value; }
get { return _store.GetOrDefault("NoWarnings", false); }
}
public string OptionCompare
{
set { _store["OptionCompare"] = value; }
get { return (string)_store["OptionCompare"]; }
}
public bool OptionExplicit
{
set { _store["OptionExplicit"] = value; }
get { return _store.GetOrDefault("OptionExplicit", true); }
}
public bool OptionStrict
{
set { _store["OptionStrict"] = value; }
get { return _store.GetOrDefault("OptionStrict", false); }
}
public bool OptionInfer
{
set { _store["OptionInfer"] = value; }
get { return _store.GetOrDefault("OptionInfer", false); }
}
// Currently only /optionstrict:custom
public string OptionStrictType
{
set { _store["OptionStrictType"] = value; }
get { return (string)_store["OptionStrictType"]; }
}
public bool RemoveIntegerChecks
{
set { _store["RemoveIntegerChecks"] = value; }
get { return _store.GetOrDefault("RemoveIntegerChecks", false); }
}
public string RootNamespace
{
set { _store["RootNamespace"] = value; }
get { return (string)_store["RootNamespace"]; }
}
public string SdkPath
{
set { _store["SdkPath"] = value; }
get { return (string)_store["SdkPath"]; }
}
/// <summary>
/// Name of the language passed to "/preferreduilang" compiler option.
/// </summary>
/// <remarks>
/// If set to null, "/preferreduilang" option is omitted, and vbc.exe uses its default setting.
/// Otherwise, the value is passed to "/preferreduilang" as is.
/// </remarks>
public string PreferredUILang
{
set { _store["PreferredUILang"] = value; }
get { return (string)_store["PreferredUILang"]; }
}
public string VsSessionGuid
{
set { _store["VsSessionGuid"] = value; }
get { return (string)_store["VsSessionGuid"]; }
}
public bool TargetCompactFramework
{
set { _store["TargetCompactFramework"] = value; }
get { return _store.GetOrDefault("TargetCompactFramework", false); }
}
public bool UseHostCompilerIfAvailable
{
set { this.useHostCompilerIfAvailable = value; }
get { return this.useHostCompilerIfAvailable; }
}
public string VBRuntimePath
{
set { _store["VBRuntimePath"] = value; }
get { return (string)_store["VBRuntimePath"]; }
}
public string Verbosity
{
set { _store["Verbosity"] = value; }
get { return (string)_store["Verbosity"]; }
}
public string WarningsAsErrors
{
set { _store["WarningsAsErrors"] = value; }
get { return (string)_store["WarningsAsErrors"]; }
}
public string WarningsNotAsErrors
{
set { _store["WarningsNotAsErrors"] = value; }
get { return (string)_store["WarningsNotAsErrors"]; }
}
public string VBRuntime
{
set { _store["VBRuntime"] = value; }
get { return (string)_store["VBRuntime"]; }
}
public string PdbFile
{
set { _store["PdbFile"] = value; }
get { return (string)_store["PdbFile"]; }
}
#endregion
#region Tool Members
/// <summary>
/// Return the name of the tool to execute.
/// </summary>
override protected string ToolName
{
get
{
return "vbc2.exe";
}
}
/// <summary>
/// Override Execute so that we can moved the PDB file if we need to,
/// after the compiler is done.
/// </summary>
public override bool Execute()
{
if (!base.Execute())
{
return false;
}
MovePdbFileIfNecessary(OutputAssembly.ItemSpec);
return !Log.HasLoggedErrors;
}
/// <summary>
/// Move the PDB file if the PDB file that was generated by the compiler
/// is not at the specified path, or if it is newer than the one there.
/// VBC does not have a switch to specify the PDB path, so we are essentially implementing that for it here.
/// We need make this possible to avoid colliding with the PDB generated by WinMDExp.
///
/// If at some future point VBC.exe offers a /pdbfile switch, this function can be removed.
/// </summary>
internal void MovePdbFileIfNecessary(string outputAssembly)
{
// Get the name of the output assembly because the pdb will be written beside it and will have the same name
if (String.IsNullOrEmpty(PdbFile) || String.IsNullOrEmpty(outputAssembly))
{
return;
}
try
{
string actualPdb = Path.ChangeExtension(outputAssembly, ".pdb"); // This is the pdb that the compiler generated
FileInfo actualPdbInfo = new FileInfo(actualPdb);
string desiredLocation = PdbFile;
if (!desiredLocation.EndsWith(".pdb", StringComparison.OrdinalIgnoreCase))
{
desiredLocation += ".pdb";
}
FileInfo desiredPdbInfo = new FileInfo(desiredLocation);
// If the compiler generated a pdb..
if (actualPdbInfo.Exists)
{
// .. and the desired one does not exist or it's older...
if (!desiredPdbInfo.Exists || (desiredPdbInfo.Exists && actualPdbInfo.LastWriteTime > desiredPdbInfo.LastWriteTime))
{
// Delete the existing one if it's already there, as Move would otherwise fail
if (desiredPdbInfo.Exists)
{
Utilities.DeleteNoThrow(desiredPdbInfo.FullName);
}
// Move the file to where we actually wanted VBC to put it
File.Move(actualPdbInfo.FullName, desiredLocation);
}
}
}
catch (Exception e) when (Utilities.IsIoRelatedException(e))
{
Log.LogErrorWithCodeFromResources("VBC.RenamePDB", PdbFile, e.Message);
}
}
/// <summary>
/// Generate the path to the tool
/// </summary>
override protected string GenerateFullPathToTool()
{
string pathToTool = ToolLocationHelper.GetPathToBuildToolsFile(ToolName, ToolLocationHelper.CurrentToolsVersion);
if (null == pathToTool)
{
pathToTool = ToolLocationHelper.GetPathToDotNetFrameworkFile(ToolName, TargetDotNetFrameworkVersion.VersionLatest);
if (null == pathToTool)
{
Log.LogErrorWithCodeFromResources("General.FrameworksFileNotFound", ToolName, ToolLocationHelper.GetDotNetFrameworkVersionFolderPrefix(TargetDotNetFrameworkVersion.VersionLatest));
}
}
return pathToTool;
}
/// <summary>
/// vbc.exe only takes the BaseAddress in hexadecimal format. But we allow the caller
/// of the task to pass in the BaseAddress in either decimal or hexadecimal format.
/// Examples of supported hex formats include "0x10000000" or "&amp;H10000000".
/// </summary>
internal string GetBaseAddressInHex()
{
string originalBaseAddress = this.BaseAddress;
if (originalBaseAddress != null)
{
if (originalBaseAddress.Length > 2)
{
string twoLetterPrefix = originalBaseAddress.Substring(0, 2);
if (
(0 == String.Compare(twoLetterPrefix, "0x", StringComparison.OrdinalIgnoreCase)) ||
(0 == String.Compare(twoLetterPrefix, "&h", StringComparison.OrdinalIgnoreCase))
)
{
// The incoming string is already in hex format ... we just need to
// remove the 0x or &H from the beginning.
return originalBaseAddress.Substring(2);
}
}
// The incoming BaseAddress is not in hexadecimal format, so we need to
// convert it to hex.
try
{
uint baseAddressDecimal = UInt32.Parse(originalBaseAddress, CultureInfo.InvariantCulture);
return baseAddressDecimal.ToString("X", CultureInfo.InvariantCulture);
}
catch (FormatException e)
{
throw Utilities.GetLocalizedArgumentException(e,
ErrorString.Vbc_ParameterHasInvalidValue, "BaseAddress", originalBaseAddress);
}
}
return null;
}
/// <summary>
/// Looks at all the parameters that have been set, and builds up the string
/// containing all the command-line switches.
/// </summary>
/// <param name="commandLine"></param>
/// <owner>RGoel, JomoF</owner>
protected internal override void AddResponseFileCommands(CommandLineBuilderExtension commandLine)
{
commandLine.AppendSwitchIfNotNull("/baseaddress:", this.GetBaseAddressInHex());
commandLine.AppendSwitchIfNotNull("/libpath:", this.AdditionalLibPaths, ",");
commandLine.AppendSwitchIfNotNull("/imports:", this.Imports, ",");
// Make sure this /doc+ switch comes *before* the /doc:<file> switch (which is handled in the
// ManagedCompiler.cs base class). /doc+ is really just an alias for /doc:<assemblyname>.xml,
// and the last /doc switch on the command-line wins. If the user provided a specific doc filename,
// we want that one to win.
commandLine.AppendPlusOrMinusSwitch("/doc", this._store, "GenerateDocumentation");
commandLine.AppendSwitchIfNotNull("/optioncompare:", this.OptionCompare);
commandLine.AppendPlusOrMinusSwitch("/optionexplicit", this._store, "OptionExplicit");
// Make sure this /optionstrict+ switch appears *before* the /optionstrict:xxxx switch below
/* twhitney: In Orcas a change was made for devdiv bug 16889 that set Option Strict-, whenever this.DisabledWarnings was
* empty. That was clearly the wrong thing to do and we found it when we had a project with all the warning configuration
* entries set to WARNING. Because this.DisabledWarnings was empty in that case we would end up sending /OptionStrict-
* effectively silencing all the warnings that had been selected.
*
* Now what we do is:
* If option strict+ is specified, that trumps everything and we just set option strict+
* Otherwise, just set option strict:custom.
* You may wonder why we don't try to set Option Strict- The reason is that Option Strict- just implies a certain
* set of warnings that should be disabled (there's ten of them today) You get the same effect by sending
* option strict:custom on along with the correct list of disabled warnings.
* Rather than make this code know the current set of disabled warnings that comprise Option strict-, we just send
* option strict:custom on with the understanding that we'll get the same behavior as option strict- since we are passing
* the /nowarn line on that contains all the warnings OptionStrict- would disable anyway. The IDE knows what they are
* and puts them in the project file so we are good. And by not making this code aware of which warnings comprise
* Option Strict-, we have one less place we have to keep up to date in terms of what comprises option strict-
*/
// Decide whether we are Option Strict+ or Option Strict:custom
object optionStrictSetting = this._store["OptionStrict"];
bool optionStrict = optionStrictSetting != null ? (bool)optionStrictSetting : false;
if (optionStrict)
{
commandLine.AppendSwitch("/optionstrict+");
}
else // OptionStrict+ wasn't specified so use :custom.
{
commandLine.AppendSwitch("/optionstrict:custom");
}
commandLine.AppendSwitchIfNotNull("/optionstrict:", this.OptionStrictType);
commandLine.AppendWhenTrue("/nowarn", this._store, "NoWarnings");
commandLine.AppendSwitchWithSplitting("/nowarn:", this.DisabledWarnings, ",", ';', ',');
commandLine.AppendPlusOrMinusSwitch("/optioninfer", this._store, "OptionInfer");
commandLine.AppendWhenTrue("/nostdlib", this._store, "NoStandardLib");
commandLine.AppendWhenTrue("/novbruntimeref", this._store, "NoVBRuntimeReference");
commandLine.AppendSwitchIfNotNull("/errorreport:", this.ErrorReport);
commandLine.AppendSwitchIfNotNull("/platform:", this.PlatformWith32BitPreference);
commandLine.AppendPlusOrMinusSwitch("/removeintchecks", this._store, "RemoveIntegerChecks");
commandLine.AppendSwitchIfNotNull("/rootnamespace:", this.RootNamespace);
commandLine.AppendSwitchIfNotNull("/sdkpath:", this.SdkPath);
commandLine.AppendSwitchIfNotNull("/langversion:", this.LangVersion);
commandLine.AppendSwitchIfNotNull("/moduleassemblyname:", this.ModuleAssemblyName);
commandLine.AppendWhenTrue("/netcf", this._store, "TargetCompactFramework");
commandLine.AppendSwitchIfNotNull("/preferreduilang:", this.PreferredUILang);
commandLine.AppendPlusOrMinusSwitch("/highentropyva", this._store, "HighEntropyVA");
if (0 == String.Compare(this.VBRuntimePath, this.VBRuntime, StringComparison.OrdinalIgnoreCase))
{
commandLine.AppendSwitchIfNotNull("/vbruntime:", this.VBRuntimePath);
}
else if (this.VBRuntime != null)
{
string vbRuntimeSwitch = this.VBRuntime;
if (0 == String.Compare(vbRuntimeSwitch, "EMBED", StringComparison.OrdinalIgnoreCase))
{
commandLine.AppendSwitch("/vbruntime*");
}
else if (0 == String.Compare(vbRuntimeSwitch, "NONE", StringComparison.OrdinalIgnoreCase))
{
commandLine.AppendSwitch("/vbruntime-");
}
else if (0 == String.Compare(vbRuntimeSwitch, "DEFAULT", StringComparison.OrdinalIgnoreCase))
{
commandLine.AppendSwitch("/vbruntime+");
}
else
{
commandLine.AppendSwitchIfNotNull("/vbruntime:", vbRuntimeSwitch);
}
}
// Verbosity
if (
(this.Verbosity != null) &&
(
(0 == String.Compare(this.Verbosity, "quiet", StringComparison.OrdinalIgnoreCase)) ||
(0 == String.Compare(this.Verbosity, "verbose", StringComparison.OrdinalIgnoreCase))
)
)
{
commandLine.AppendSwitchIfNotNull("/", this.Verbosity);
}
commandLine.AppendSwitchIfNotNull("/doc:", this.DocumentationFile);
commandLine.AppendSwitchUnquotedIfNotNull("/define:", Vbc.GetDefineConstantsSwitch(this.DefineConstants));
AddReferencesToCommandLine(commandLine);
commandLine.AppendSwitchIfNotNull("/win32resource:", this.Win32Resource);
// Special case for "Sub Main" (See VSWhidbey 381254)
if (0 != String.Compare("Sub Main", this.MainEntryPoint, StringComparison.OrdinalIgnoreCase))
{
commandLine.AppendSwitchIfNotNull("/main:", this.MainEntryPoint);
}
base.AddResponseFileCommands(commandLine);
// This should come after the "TreatWarningsAsErrors" flag is processed (in managedcompiler.cs).
// Because if TreatWarningsAsErrors=false, then we'll have a /warnaserror- on the command-line,
// and then any specific warnings that should be treated as errors should be specified with
// /warnaserror+:<list> after the /warnaserror- switch. The order of the switches on the command-line
// does matter.
//
// Note that
// /warnaserror+
// is just shorthand for:
// /warnaserror+:<all possible warnings>
//
// Similarly,
// /warnaserror-
// is just shorthand for:
// /warnaserror-:<all possible warnings>
commandLine.AppendSwitchWithSplitting("/warnaserror+:", this.WarningsAsErrors, ",", ';', ',');
commandLine.AppendSwitchWithSplitting("/warnaserror-:", this.WarningsNotAsErrors, ",", ';', ',');
// If not design time build and the globalSessionGuid property was set then add a -globalsessionguid:<guid>
bool designTime = false;
if (this.HostObject != null)
{
var vbHost = this.HostObject as IVbcHostObject;
designTime = vbHost.IsDesignTime();
}
if (!designTime)
{
if (!string.IsNullOrWhiteSpace(this.VsSessionGuid))
{
commandLine.AppendSwitchIfNotNull("/sqmsessionguid:", this.VsSessionGuid);
}
}
// It's a good idea for the response file to be the very last switch passed, just
// from a predictability perspective. It also solves the problem that a dogfooder
// ran into, which is described in an email thread attached to bug VSWhidbey 146883.
// See also bugs 177762 and 118307 for additional bugs related to response file position.
if (this.ResponseFiles != null)
{
foreach (ITaskItem response in this.ResponseFiles)
{
commandLine.AppendSwitchIfNotNull("@", response.ItemSpec);
}
}
}
void AddReferencesToCommandLine(CommandLineBuilderExtension commandLine)
{
if ((this.References == null) || (this.References.Length == 0))
{
return;
}
var references = new List<ITaskItem>(this.References.Length);
var links = new List<ITaskItem>(this.References.Length);
foreach (ITaskItem reference in this.References)
{
bool embed = Utilities.TryConvertItemMetadataToBool(reference, "EmbedInteropTypes");
if (embed)
{
links.Add(reference);
}
else
{
references.Add(reference);
}
}
if (links.Count > 0)
{
commandLine.AppendSwitchIfNotNull("/link:", links.ToArray(), ",");
}
if (references.Count > 0)
{
commandLine.AppendSwitchIfNotNull("/reference:", references.ToArray(), ",");
}
}
/// <summary>
/// Validate parameters, log errors and warnings and return true if
/// Execute should proceed.
/// </summary>
override protected bool ValidateParameters()
{
if (!base.ValidateParameters())
{
return false;
}
// Validate that the "Verbosity" parameter is one of "quiet", "normal", or "verbose".
if (this.Verbosity != null)
{
if ((0 != String.Compare(Verbosity, "normal", StringComparison.OrdinalIgnoreCase)) &&
(0 != String.Compare(Verbosity, "quiet", StringComparison.OrdinalIgnoreCase)) &&
(0 != String.Compare(Verbosity, "verbose", StringComparison.OrdinalIgnoreCase)))
{
Log.LogErrorWithCodeFromResources("Vbc.EnumParameterHasInvalidValue", "Verbosity", this.Verbosity, "Quiet, Normal, Verbose");
return false;
}
}
return true;
}
/// <summary>
/// This method intercepts the lines to be logged coming from STDOUT from VBC.
/// Once we see a standard vb warning or error, then we capture it and grab the next 3
/// lines so we can transform the string form the form of FileName.vb(line) to FileName.vb(line,column)
/// which will allow us to report the line and column to the IDE, and thus filter the error
/// in the duplicate case for multi-targeting, or just squiggle the appropriate token
/// instead of the entire line.
/// </summary>
/// <param name="singleLine">A single line from the STDOUT of the vbc compiler</param>
/// <param name="messageImportance">High,Low,Normal</param>
protected override void LogEventsFromTextOutput(string singleLine, MessageImportance messageImportance)
{
// We can return immediately if this was not called by the out of proc compiler
if (!this.UsedCommandLineTool)
{
base.LogEventsFromTextOutput(singleLine, messageImportance);
return;
}
// We can also return immediately if the current string is not a warning or error
// and we have not seen a warning or error yet. 'Error' and 'Warning' are not localized.
if (vbErrorLines.Count == 0 &&
singleLine.IndexOf("warning", StringComparison.OrdinalIgnoreCase) == -1 &&
singleLine.IndexOf("error", StringComparison.OrdinalIgnoreCase) == -1)
{
base.LogEventsFromTextOutput(singleLine, messageImportance);
return;
}
ParseVBErrorOrWarning(singleLine, messageImportance);
}
/// <summary>
/// Given a string, parses it to find out whether it's an error or warning and, if so,
/// make sure it's validated properly.
/// </summary>
/// <comments>
/// INTERNAL FOR UNITTESTING ONLY
/// </comments>
/// <param name="singleLine">The line to parse</param>
/// <param name="messageImportance">The MessageImportance to use when reporting the error.</param>
internal void ParseVBErrorOrWarning(string singleLine, MessageImportance messageImportance)
{
// if this string is empty then we haven't seen the first line of an error yet
if (vbErrorLines.Count > 0)
{
// vbc separates the error message from the source text with an empty line, so
// we can check for an empty line to see if vbc finished outputting the error message
if (!isDoneOutputtingErrorMessage && singleLine.Length == 0)
{
isDoneOutputtingErrorMessage = true;
numberOfLinesInErrorMessage = vbErrorLines.Count;
}
vbErrorLines.Enqueue(new VBError(singleLine, messageImportance));
// We are looking for the line that indicates the column (contains the '~'),
// which vbc outputs 3 lines below the error message:
//
// <error message>
// <blank line>
// <line with the source text>
// <line with the '~'>
if (isDoneOutputtingErrorMessage &&
vbErrorLines.Count == numberOfLinesInErrorMessage + 3)
{
// Once we have the 4th line (error line + 3), then parse it for the first ~
// which will correspond to the column of the token with the error because
// VBC respects the users's indentation settings in the file it is compiling
// and only outputs SPACE chars to STDOUT.
// The +1 is to translate the index into user columns which are 1 based.
VBError originalVBError = vbErrorLines.Dequeue();
string originalVBErrorString = originalVBError.Message;
int column = singleLine.IndexOf('~') + 1;
int endParenthesisLocation = originalVBErrorString.IndexOf(')');
// If for some reason the line does not contain any ~ then something went wrong
// so abort and return the origional string.
if (column < 0 || endParenthesisLocation < 0)
{
// we need to output all of the original lines we ate.
Log.LogMessageFromText(originalVBErrorString, originalVBError.MessageImportance);
foreach (VBError vberror in vbErrorLines)
{
base.LogEventsFromTextOutput(vberror.Message, vberror.MessageImportance);
}
vbErrorLines.Clear();
return;
}
string newLine = null;
newLine = originalVBErrorString.Substring(0, endParenthesisLocation) + "," + column + originalVBErrorString.Substring(endParenthesisLocation);
// Output all of the lines of the error, but with the modified first line as well.
Log.LogMessageFromText(newLine, originalVBError.MessageImportance);
foreach (VBError vberror in vbErrorLines)
{
base.LogEventsFromTextOutput(vberror.Message, vberror.MessageImportance);
}
vbErrorLines.Clear();
}
}
else
{
CanonicalError.Parts parts = CanonicalError.Parse(singleLine);
if (parts == null)
{
base.LogEventsFromTextOutput(singleLine, messageImportance);
}
else if ((parts.category == CanonicalError.Parts.Category.Error ||
parts.category == CanonicalError.Parts.Category.Warning) &&
parts.column == CanonicalError.Parts.numberNotSpecified)
{
if (parts.line != CanonicalError.Parts.numberNotSpecified)
{
// If we got here, then this is a standard VBC error or warning.
vbErrorLines.Enqueue(new VBError(singleLine, messageImportance));
isDoneOutputtingErrorMessage = false;
numberOfLinesInErrorMessage = 0;
}
else
{
// Project-level errors don't have line numbers -- just output now.
base.LogEventsFromTextOutput(singleLine, messageImportance);
}
}
}
}
#endregion
/// <summary>
/// Many VisualStudio VB projects have values for the DefineConstants property that
/// contain quotes and spaces. Normally we don't allow parameters passed into the
/// task to contain quotes, because if we weren't careful, we might accidently
/// allow a parameter injection attach. But for "DefineConstants", we have to allow
/// it.
/// So this method prepares the string to be passed in on the /define: command-line
/// switch. It does that by quoting the entire string, and escaping the embedded
/// quotes.
/// </summary>
internal static string GetDefineConstantsSwitch
(
string originalDefineConstants
)
{
if ((originalDefineConstants == null) || (originalDefineConstants.Length == 0))
{
return null;
}
StringBuilder finalDefineConstants = new StringBuilder(originalDefineConstants);
// Replace slash-quote with slash-slash-quote.
finalDefineConstants.Replace("\\\"", "\\\\\"");
// Replace quote with slash-quote.
finalDefineConstants.Replace("\"", "\\\"");
// Surround the whole thing with a pair of double-quotes.
finalDefineConstants.Insert(0, '"');
finalDefineConstants.Append('"');
// Now it's ready to be passed in to the /define: switch.
return finalDefineConstants.ToString();
}
/// <summary>
/// private class that just holds together name, value pair for the vbErrorLines Queue
/// </summary>
private class VBError
{
public string Message { get; set; }
public MessageImportance MessageImportance { get; set; }
public VBError(string message, MessageImportance importance)
{
this.Message = message;
this.MessageImportance = importance;
}
}
}
}
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<packages />
......@@ -321,7 +321,7 @@ Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "SharedCollections", "Compil
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CSharpWinRTTest", "Compilers\CSharp\Test\WinRT\CSharpWinRTTest.csproj", "{FCFA8808-A1B6-48CC-A1EA-0B8CA8AEDA8E}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Pdb2Xml", "Tools\Source\Pdb2Xml\Pdb2Xml.csproj", "{CF450DCE-D12B-4A11-8D2D-A7A125372C48}"
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MSBuildTask", "Compilers\Core\MSBuildTask\MSBuildTask.csproj", "{D874349C-8BB3-4BDC-8535-2D52CCCA1198}"
EndProject
Global
GlobalSection(SharedMSBuildProjectFiles) = preSolution
......@@ -1862,6 +1862,18 @@ Global
{CF450DCE-D12B-4A11-8D2D-A7A125372C48}.Release|ARM.Build.0 = Release|Any CPU
{CF450DCE-D12B-4A11-8D2D-A7A125372C48}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
{CF450DCE-D12B-4A11-8D2D-A7A125372C48}.Release|Mixed Platforms.Build.0 = Release|Any CPU
{D874349C-8BB3-4BDC-8535-2D52CCCA1198}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D874349C-8BB3-4BDC-8535-2D52CCCA1198}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D874349C-8BB3-4BDC-8535-2D52CCCA1198}.Debug|ARM.ActiveCfg = Debug|Any CPU
{D874349C-8BB3-4BDC-8535-2D52CCCA1198}.Debug|ARM.Build.0 = Debug|Any CPU
{D874349C-8BB3-4BDC-8535-2D52CCCA1198}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
{D874349C-8BB3-4BDC-8535-2D52CCCA1198}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
{D874349C-8BB3-4BDC-8535-2D52CCCA1198}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D874349C-8BB3-4BDC-8535-2D52CCCA1198}.Release|Any CPU.Build.0 = Release|Any CPU
{D874349C-8BB3-4BDC-8535-2D52CCCA1198}.Release|ARM.ActiveCfg = Release|Any CPU
{D874349C-8BB3-4BDC-8535-2D52CCCA1198}.Release|ARM.Build.0 = Release|Any CPU
{D874349C-8BB3-4BDC-8535-2D52CCCA1198}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
{D874349C-8BB3-4BDC-8535-2D52CCCA1198}.Release|Mixed Platforms.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
......@@ -2011,6 +2023,7 @@ Global
{D0BC9BE7-24F6-40CA-8DC6-FCB93BD44B34} = {A41D1B99-F489-4C43-BBDF-96D61B19A6B9}
{C1930979-C824-496B-A630-70F5369A636F} = {A41D1B99-F489-4C43-BBDF-96D61B19A6B9}
{FCFA8808-A1B6-48CC-A1EA-0B8CA8AEDA8E} = {32A48625-F0AD-419D-828B-A50BDABA38EA}
{D874349C-8BB3-4BDC-8535-2D52CCCA1198} = {A41D1B99-F489-4C43-BBDF-96D61B19A6B9}
{CF450DCE-D12B-4A11-8D2D-A7A125372C48} = {64BDF58B-41BA-A19E-0D34-B5FA598403B6}
EndGlobalSection
EndGlobal
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册