Skip to content

Commit

Permalink
Merge pull request #160 from rainersigwald/xplat
Browse files Browse the repository at this point in the history
Merge Unicode-related exec changes into xplat
  • Loading branch information
rainersigwald committed Aug 24, 2015
2 parents 9987c90 + fbbfa50 commit 14004e7
Show file tree
Hide file tree
Showing 3 changed files with 99 additions and 141 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -1007,14 +1007,7 @@ public void ParseSolutionConfigurationWithEmptyLines()
EndGlobal
";

try
{
SolutionFile solution = ParseSolutionHelper(solutionFileContents);
}
catch (InvalidProjectFileException ex)
{
Assert.Fail();
}
ParseSolutionHelper(solutionFileContents);
}

/// <summary>
Expand Down
205 changes: 72 additions & 133 deletions src/XMakeTasks/Exec.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,9 @@

using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.IO;
using System.Text.RegularExpressions;

using Microsoft.Build.Framework;
using Microsoft.Build.Shared;
using Microsoft.Build.Utilities;
Expand All @@ -29,7 +28,14 @@ public class Exec : ToolTaskExtension
/// </summary>
public Exec()
{
// do nothing
Command = string.Empty;

// Console-based output uses the current system OEM code page by default. Note that we should not use Console.OutputEncoding
// here since processes we run don't really have much to do with our console window (and also Console.OutputEncoding
// doesn't return the OEM code page if the running application that hosts MSBuild is not a console application).
// Note: 8/12/15 - Changed encoding to use UTF8 when OS version is greater than or equal to 6.1 (Windows 7)
_standardOutputEncoding = GetEncodingWithOsFallback();
_standardErrorEncoding = GetEncodingWithOsFallback();
}

#endregion
Expand All @@ -38,81 +44,35 @@ public Exec()

// Are the ecodings for StdErr and StdOut streams valid
private bool _encodingParametersValid = true;
private string _command = String.Empty;
private string _userSpecifiedWorkingDirectory;
private string _workingDirectory;
private bool _ignoreExitCode = false;
private bool _consoleToMSBuild = false;
private ITaskItem[] _outputs;
internal bool workingDirectoryIsUNC = false; // internal for unit testing
internal bool workingDirectoryIsUNC; // internal for unit testing
private string _batchFile;
private string _customErrorRegex;
private string _customWarningRegex;
private bool _ignoreStandardErrorWarningFormat = false; // By default, detect standard-format errors
private List<ITaskItem> _nonEmptyOutput = new List<ITaskItem>();
private readonly List<ITaskItem> _nonEmptyOutput = new List<ITaskItem>();
private Encoding _standardErrorEncoding;
private Encoding _standardOutputEncoding;

#endregion

#region Properties

[Required]
public string Command
{
get
{
return _command;
}

set
{
_command = value;
}
}
public string Command { get; set; }

public string WorkingDirectory
{
get
{
return _userSpecifiedWorkingDirectory;
}

set
{
_userSpecifiedWorkingDirectory = value;
}
}

public bool IgnoreExitCode
{
get
{
return _ignoreExitCode;
}
public string WorkingDirectory { get; set; }

set
{
_ignoreExitCode = value;
}
}
public bool IgnoreExitCode { get; set; }

/// <summary>
/// Enable the pipe of the standard out to an item (StandardOutput).
/// </summary>
/// <Remarks>
/// Even thought this is called a pipe, it is infact a Tee. Use StandardOutputImportance to adjust the visibility of the stdout.
/// </Remarks>
public bool ConsoleToMSBuild
{
get
{
return _consoleToMSBuild;
}

set
{
_consoleToMSBuild = value;
}
}
public bool ConsoleToMSBuild { get; set; }

/// <summary>
/// Users can supply a regular expression that we should
Expand Down Expand Up @@ -155,13 +115,6 @@ protected override Encoding StandardOutputEncoding
get { return _standardOutputEncoding; }
}

/// <remarks>
/// Console-based output uses the current system OEM code page by default. Note that we should not use Console.OutputEncoding
/// here since processes we run don't really have much to do with our console window (and also Console.OutputEncoding
/// doesn't return the OEM code page if the running application that hosts MSBuild is not a console application).
/// </remarks>
private Encoding _standardOutputEncoding = EncodingUtilities.CurrentSystemOemEncoding;

/// <summary>
/// Property specifying the encoding of the captured task standard error stream
/// </summary>
Expand All @@ -170,13 +123,6 @@ protected override Encoding StandardErrorEncoding
get { return _standardErrorEncoding; }
}

/// <remarks>
/// Console-based output uses the current system OEM code page by default. Note that we should not use Console.OutputEncoding
/// here since processes we run don't really have much to do with our console window (and also Console.OutputEncoding
/// doesn't return the OEM code page if the running application that hosts MSBuild is not a console application).
/// </remarks>
private Encoding _standardErrorEncoding = EncodingUtilities.CurrentSystemOemEncoding;

/// <summary>
/// Project visible property specifying the encoding of the captured task standard output stream
/// </summary>
Expand Down Expand Up @@ -222,22 +168,8 @@ public string StdErrEncoding
[Output]
public ITaskItem[] Outputs
{
get
{
if (_outputs == null)
{
return new ITaskItem[0];
}
else
{
return _outputs;
}
}

set
{
_outputs = value;
}
get { return _outputs ?? new ITaskItem[0]; }
set { _outputs = value; }
}

/// <summary>
Expand All @@ -248,18 +180,7 @@ public ITaskItem[] Outputs
[Output]
public ITaskItem[] ConsoleOutput
{
get
{
if (!ConsoleToMSBuild)
{
return new ITaskItem[0];
}
else
{
return _nonEmptyOutput.ToArray();
}
}
set { }
get { return !ConsoleToMSBuild ? new ITaskItem[0] : _nonEmptyOutput.ToArray(); }
}

#endregion
Expand All @@ -279,8 +200,8 @@ private void CreateTemporaryBatchFile()
// We need to get the current OEM code page which will be the same language as the current ANSI code page,
// just the OEM version.
// See http://www.microsoft.com/globaldev/getWR/steps/wrg_codepage.mspx for a discussion of ANSI vs OEM

using (StreamWriter sw = new StreamWriter(_batchFile, false, isUnix ? Encoding.ASCII : EncodingUtilities.CurrentSystemOemEncoding)) // HIGHCHAR: Exec task batch files are in OEM code pages (not ANSI!)
// Note: 8/12/15 - Switched to use UTF8 on OS newer than 6.1 (Windows 7)
using (StreamWriter sw = new StreamWriter(_batchFile, false, GetEncodingWithOsFallback()))
{
if (!isUnix)
{
Expand All @@ -299,6 +220,13 @@ private void CreateTemporaryBatchFile()
sw.WriteLine("set errorlevel=dummy");
sw.WriteLine("set errorlevel=");

// We probably have to change the code page to UTF8 (65001) for non-ansi characters to work.
if (EncodingUtilities.CurrentSystemOemEncoding.CodePage != sw.Encoding.CodePage)
{
// Output to nul so we don't change output and logs.
sw.WriteLine(string.Format("chcp {0}>nul", sw.Encoding.CodePage));
}

// if the working directory is a UNC path, bracket the exec command with pushd and popd, because pushd
// automatically maps the network path to a drive letter, and then popd disconnects it
if (workingDirectoryIsUNC)
Expand Down Expand Up @@ -393,13 +321,12 @@ protected override int ExecuteTool(string pathToTool, string responseFileCommand
/// </remarks>
protected override bool HandleTaskExecutionErrors()
{
if (_ignoreExitCode)
if (IgnoreExitCode)
{
Log.LogMessageFromResources(MessageImportance.Normal, "Exec.CommandFailedNoErrorCode", this.Command, ExitCode);
return true;
}
else
{

if (ExitCode == NativeMethods.SE_ERR_ACCESSDENIED)
{
Log.LogErrorWithCodeFromResources("Exec.CommandFailedAccessDenied", this.Command, ExitCode);
Expand All @@ -410,7 +337,6 @@ protected override bool HandleTaskExecutionErrors()
}
return false;
}
}

/// <summary>
/// Logs the tool name and the path from where it is being run.
Expand All @@ -421,7 +347,6 @@ protected override bool HandleTaskExecutionErrors()
protected override void LogPathToTool(string toolName, string pathToTool)
{
// Do nothing
return;
}

/// <summary>
Expand All @@ -431,10 +356,7 @@ protected override void LogPathToTool(string toolName, string pathToTool)
/// Overridden to log the batch file command instead of the cmd.exe command.
/// </remarks>
/// <param name="message"></param>
protected override void LogToolCommand
(
string message
)
protected override void LogToolCommand(string message)
{
//Dont print the command line if Echo is Off.
if (!EchoOff)
Expand All @@ -450,11 +372,7 @@ string message
/// <remarks>
/// Overridden to handle any custom regular expressions supplied.
/// </remarks>
protected override void LogEventsFromTextOutput
(
string singleLine,
MessageImportance messageImportance
)
protected override void LogEventsFromTextOutput(string singleLine, MessageImportance messageImportance)
{
if (OutputMatchesRegex(singleLine, ref _customErrorRegex))
{
Expand Down Expand Up @@ -539,8 +457,8 @@ protected override bool ValidateParameters()

// determine what the working directory for the exec command is going to be -- if the user specified a working
// directory use that, otherwise it's the current directory
_workingDirectory = ((_userSpecifiedWorkingDirectory != null) && (_userSpecifiedWorkingDirectory.Length > 0))
? _userSpecifiedWorkingDirectory
_workingDirectory = !string.IsNullOrEmpty(WorkingDirectory)
? WorkingDirectory
: Directory.GetCurrentDirectory();

// check if the working directory we're going to use for the exec command is a UNC path
Expand Down Expand Up @@ -575,8 +493,8 @@ protected override string GenerateFullPathToTool()
// Get the fully qualified path to cmd.exe
if (Path.DirectorySeparatorChar == '\\')
{
return ToolLocationHelper.GetPathToSystemFile("cmd.exe");
}
return ToolLocationHelper.GetPathToSystemFile("cmd.exe");
}
else
{
return "sh";
Expand Down Expand Up @@ -637,17 +555,17 @@ protected internal override void AddCommandLineCommands(CommandLineBuilderExtens

if (NativeMethodsShared.IsWindows)
{
commandLine.AppendSwitch("/Q"); // echo off
commandLine.AppendSwitch("/C"); // run then terminate
commandLine.AppendSwitch("/Q"); // echo off
commandLine.AppendSwitch("/C"); // run then terminate

// If for some crazy reason the path has a & character and a space in it
// then get the short path of the temp path, which should not have spaces in it
// and then escape the &
if (batchFileForCommandLine.Contains("&") && !batchFileForCommandLine.Contains("^&"))
{
batchFileForCommandLine = NativeMethodsShared.GetShortFilePath(batchFileForCommandLine);
batchFileForCommandLine = batchFileForCommandLine.Replace("&", "^&");
}
// If for some crazy reason the path has a & character and a space in it
// then get the short path of the temp path, which should not have spaces in it
// and then escape the &
if (batchFileForCommandLine.Contains("&") && !batchFileForCommandLine.Contains("^&"))
{
batchFileForCommandLine = NativeMethodsShared.GetShortFilePath(batchFileForCommandLine);
batchFileForCommandLine = batchFileForCommandLine.Replace("&", "^&");
}
}
commandLine.AppendFileNameIfNotNull(batchFileForCommandLine);
}
Expand All @@ -661,10 +579,7 @@ protected internal override void AddCommandLineCommands(CommandLineBuilderExtens
/// </summary>
protected override string ToolName
{
get
{
return "cmd.exe";
}
get { return "cmd.exe"; }
}

/// <summary>
Expand All @@ -689,5 +604,29 @@ protected override MessageImportance StandardOutputLoggingImportance
}

#endregion

private static readonly Encoding s_utf8WithoutBom = new UTF8Encoding(false);

/// <summary>
/// Get encoding based on OS. This will fall back to previous behavior on Windows before Windows 7.
/// If the OS is greater than or equal to Windows 7, UTF8 encoding will be used for the cmd file.
/// On unices, use ASCII.
/// </summary>
/// <remarks>UTF8 w/o BOM is used because cmd.exe does not like a BOM in a .cmd file.</remarks>
/// <returns>Encoding to use</returns>
private Encoding GetEncodingWithOsFallback()
{
if (Path.DirectorySeparatorChar == '/')
{
return Encoding.ASCII;
}

// Windows 7 (6.1) or greater
var windows7 = new Version(6, 1);

return Environment.OSVersion.Version >= windows7
? s_utf8WithoutBom
: EncodingUtilities.CurrentSystemOemEncoding;
}
}
}
Loading

0 comments on commit 14004e7

Please sign in to comment.