Remove dependency on Microsoft.DotNet.Cli.Utils
This commit is contained in:
parent
2a0e827f9b
commit
b6b4523993
|
|
@ -31,4 +31,5 @@ project.lock.json
|
||||||
.vscode/
|
.vscode/
|
||||||
testWorkDir/
|
testWorkDir/
|
||||||
*.nuget.props
|
*.nuget.props
|
||||||
*.nuget.targets
|
*.nuget.targets
|
||||||
|
.idea/
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
// Copyright (c) .NET Foundation. All rights reserved.
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using Microsoft.DotNet.Cli.Utils;
|
|
||||||
using Microsoft.DotNet.Watcher.Tools;
|
using Microsoft.DotNet.Watcher.Tools;
|
||||||
using Microsoft.DotNet.Watcher.Internal;
|
using Microsoft.DotNet.Watcher.Internal;
|
||||||
using Microsoft.Extensions.CommandLineUtils;
|
using Microsoft.Extensions.CommandLineUtils;
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.DotNet.Watcher.Internal;
|
using Microsoft.DotNet.Watcher.Internal;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
using Microsoft.Extensions.Tools.Internal;
|
||||||
|
|
||||||
namespace Microsoft.DotNet.Watcher
|
namespace Microsoft.DotNet.Watcher
|
||||||
{
|
{
|
||||||
|
|
@ -45,6 +46,10 @@ namespace Microsoft.DotNet.Watcher
|
||||||
var fileSetTask = fileSetWatcher.GetChangedFileAsync(combinedCancellationSource.Token);
|
var fileSetTask = fileSetWatcher.GetChangedFileAsync(combinedCancellationSource.Token);
|
||||||
var processTask = _processRunner.RunAsync(processSpec, combinedCancellationSource.Token);
|
var processTask = _processRunner.RunAsync(processSpec, combinedCancellationSource.Token);
|
||||||
|
|
||||||
|
_logger.LogInformation("Running {execName} with the following arguments: {args}",
|
||||||
|
processSpec.ShortDisplayName(),
|
||||||
|
ArgumentEscaper.EscapeAndConcatenate(processSpec.Arguments));
|
||||||
|
|
||||||
var finishedTask = await Task.WhenAny(processTask, fileSetTask, cancelledTaskSource.Task);
|
var finishedTask = await Task.WhenAny(processTask, fileSetTask, cancelledTaskSource.Task);
|
||||||
|
|
||||||
// Regardless of the which task finished first, make sure everything is cancelled
|
// Regardless of the which task finished first, make sure everything is cancelled
|
||||||
|
|
|
||||||
|
|
@ -7,11 +7,11 @@ using System.Diagnostics;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
|
using System.Text;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.DotNet.Cli.Utils;
|
|
||||||
using Microsoft.DotNet.Watcher.Tools;
|
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
using Microsoft.Extensions.Tools.Internal;
|
||||||
|
|
||||||
namespace Microsoft.DotNet.Watcher.Internal
|
namespace Microsoft.DotNet.Watcher.Internal
|
||||||
{
|
{
|
||||||
|
|
@ -23,6 +23,7 @@ namespace Microsoft.DotNet.Watcher.Internal
|
||||||
private readonly string _projectFile;
|
private readonly string _projectFile;
|
||||||
private readonly string _watchTargetsDir;
|
private readonly string _watchTargetsDir;
|
||||||
private readonly OutputSink _outputSink;
|
private readonly OutputSink _outputSink;
|
||||||
|
private readonly ProcessRunner _processRunner;
|
||||||
|
|
||||||
public MsBuildFileSetFactory(ILogger logger, string projectFile)
|
public MsBuildFileSetFactory(ILogger logger, string projectFile)
|
||||||
: this(logger, projectFile, new OutputSink())
|
: this(logger, projectFile, new OutputSink())
|
||||||
|
|
@ -40,6 +41,7 @@ namespace Microsoft.DotNet.Watcher.Internal
|
||||||
_projectFile = projectFile;
|
_projectFile = projectFile;
|
||||||
_watchTargetsDir = FindWatchTargetsDir();
|
_watchTargetsDir = FindWatchTargetsDir();
|
||||||
_outputSink = outputSink;
|
_outputSink = outputSink;
|
||||||
|
_processRunner = new ProcessRunner(logger);
|
||||||
}
|
}
|
||||||
|
|
||||||
internal List<string> BuildFlags { get; } = new List<string>
|
internal List<string> BuildFlags { get; } = new List<string>
|
||||||
|
|
@ -71,20 +73,21 @@ namespace Microsoft.DotNet.Watcher.Internal
|
||||||
var capture = _outputSink.StartCapture();
|
var capture = _outputSink.StartCapture();
|
||||||
// TODO adding files doesn't currently work. Need to provide a way to detect new files
|
// TODO adding files doesn't currently work. Need to provide a way to detect new files
|
||||||
// find files
|
// find files
|
||||||
var exitCode = Command.CreateDotNet("msbuild",
|
var processSpec = new ProcessSpec
|
||||||
new[]
|
{
|
||||||
|
Executable = DotNetMuxer.MuxerPathOrDefault(),
|
||||||
|
WorkingDirectory = projectDir,
|
||||||
|
Arguments = new[]
|
||||||
{
|
{
|
||||||
_projectFile,
|
"msbuild",
|
||||||
|
_projectFile,
|
||||||
$"/p:_DotNetWatchTargetsLocation={_watchTargetsDir}", // add our dotnet-watch targets
|
$"/p:_DotNetWatchTargetsLocation={_watchTargetsDir}", // add our dotnet-watch targets
|
||||||
$"/p:_DotNetWatchListFile={watchList}",
|
$"/p:_DotNetWatchListFile={watchList}"
|
||||||
}.Concat(BuildFlags))
|
}.Concat(BuildFlags),
|
||||||
.CaptureStdErr()
|
OutputCapture = capture
|
||||||
.CaptureStdOut()
|
};
|
||||||
.OnErrorLine(l => capture.WriteErrorLine(l))
|
|
||||||
.OnOutputLine(l => capture.WriteOutputLine(l))
|
var exitCode = await _processRunner.RunAsync(processSpec, cancellationToken);
|
||||||
.WorkingDirectory(projectDir)
|
|
||||||
.Execute()
|
|
||||||
.ExitCode;
|
|
||||||
|
|
||||||
if (exitCode == 0)
|
if (exitCode == 0)
|
||||||
{
|
{
|
||||||
|
|
@ -104,9 +107,18 @@ namespace Microsoft.DotNet.Watcher.Internal
|
||||||
return fileset;
|
return fileset;
|
||||||
}
|
}
|
||||||
|
|
||||||
_logger.LogError($"Error(s) finding watch items project file '{Path.GetFileName(_projectFile)}': ");
|
var sb = new StringBuilder()
|
||||||
_logger.LogError(capture.GetAllLines("[MSBUILD] : "));
|
.Append("Error(s) finding watch items project file '")
|
||||||
_logger.LogInformation("Fix the error to continue.");
|
.Append(Path.GetFileName(_projectFile))
|
||||||
|
.AppendLine("' :");
|
||||||
|
|
||||||
|
foreach (var line in capture.Lines)
|
||||||
|
{
|
||||||
|
sb.Append(" [MSBUILD] :").AppendLine(line);
|
||||||
|
}
|
||||||
|
|
||||||
|
_logger.LogError(sb.ToString());
|
||||||
|
_logger.LogInformation("Fix the error to continue or press Ctrl+C to exit.");
|
||||||
|
|
||||||
var fileSet = new FileSet(new[] { _projectFile });
|
var fileSet = new FileSet(new[] { _projectFile });
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,6 @@
|
||||||
using System;
|
using System;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Microsoft.DotNet.Cli.Utils;
|
|
||||||
using Microsoft.DotNet.Watcher.Tools;
|
using Microsoft.DotNet.Watcher.Tools;
|
||||||
|
|
||||||
namespace Microsoft.DotNet.Watcher.Internal
|
namespace Microsoft.DotNet.Watcher.Internal
|
||||||
|
|
@ -35,12 +34,12 @@ namespace Microsoft.DotNet.Watcher.Internal
|
||||||
|
|
||||||
if (projects.Count > 1)
|
if (projects.Count > 1)
|
||||||
{
|
{
|
||||||
throw new GracefulException(Resources.FormatError_MultipleProjectsFound(projectPath));
|
throw new FileNotFoundException(Resources.FormatError_MultipleProjectsFound(projectPath));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (projects.Count == 0)
|
if (projects.Count == 0)
|
||||||
{
|
{
|
||||||
throw new GracefulException(Resources.FormatError_NoProjectsFound(projectPath));
|
throw new FileNotFoundException(Resources.FormatError_NoProjectsFound(projectPath));
|
||||||
}
|
}
|
||||||
|
|
||||||
return projects[0];
|
return projects[0];
|
||||||
|
|
@ -48,7 +47,7 @@ namespace Microsoft.DotNet.Watcher.Internal
|
||||||
|
|
||||||
if (!File.Exists(projectPath))
|
if (!File.Exists(projectPath))
|
||||||
{
|
{
|
||||||
throw new GracefulException(Resources.FormatError_ProjectPath_NotFound(projectPath));
|
throw new FileNotFoundException(Resources.FormatError_ProjectPath_NotFound(projectPath));
|
||||||
}
|
}
|
||||||
|
|
||||||
return projectPath;
|
return projectPath;
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,14 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace Microsoft.DotNet.Watcher.Internal
|
||||||
|
{
|
||||||
|
public class OutputCapture
|
||||||
|
{
|
||||||
|
private readonly List<string> _lines = new List<string>();
|
||||||
|
public IEnumerable<string> Lines => _lines;
|
||||||
|
public void AddLine(string line) => _lines.Add(line);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,27 +1,14 @@
|
||||||
// Copyright (c) .NET Foundation. All rights reserved.
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
|
|
||||||
namespace Microsoft.DotNet.Watcher.Internal
|
namespace Microsoft.DotNet.Watcher.Internal
|
||||||
{
|
{
|
||||||
internal class OutputSink
|
public class OutputSink
|
||||||
{
|
{
|
||||||
public OutputCapture Current { get; private set; }
|
public OutputCapture Current { get; private set; }
|
||||||
public OutputCapture StartCapture()
|
public OutputCapture StartCapture()
|
||||||
{
|
{
|
||||||
return (Current = new OutputCapture());
|
return (Current = new OutputCapture());
|
||||||
}
|
}
|
||||||
|
|
||||||
public class OutputCapture
|
|
||||||
{
|
|
||||||
private readonly List<string> _lines = new List<string>();
|
|
||||||
public IEnumerable<string> Lines => _lines;
|
|
||||||
public void WriteOutputLine(string line) => _lines.Add(line);
|
|
||||||
public void WriteErrorLine(string line) => _lines.Add(line);
|
|
||||||
public string GetAllLines(string prefix) => string.Join(Environment.NewLine, _lines.Select(l => prefix + l));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -3,11 +3,12 @@
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
|
using System.IO;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.Extensions.Internal;
|
using Microsoft.Extensions.Internal;
|
||||||
using Microsoft.DotNet.Cli.Utils;
|
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
using Microsoft.Extensions.Tools.Internal;
|
||||||
|
|
||||||
namespace Microsoft.DotNet.Watcher.Internal
|
namespace Microsoft.DotNet.Watcher.Internal
|
||||||
{
|
{
|
||||||
|
|
@ -35,30 +36,42 @@ namespace Microsoft.DotNet.Watcher.Internal
|
||||||
cancellationToken.Register(() => processState.TryKill());
|
cancellationToken.Register(() => processState.TryKill());
|
||||||
|
|
||||||
process.Start();
|
process.Start();
|
||||||
_logger.LogInformation("{execName} process id: {pid}", processSpec.ShortDisplayName(), process.Id);
|
|
||||||
|
|
||||||
await processState.Task;
|
if (processSpec.IsOutputCaptured)
|
||||||
|
{
|
||||||
|
await Task.WhenAll(
|
||||||
|
processState.Task,
|
||||||
|
ConsumeStreamAsync(process.StandardOutput, processSpec.OutputCapture.AddLine),
|
||||||
|
ConsumeStreamAsync(process.StandardError, processSpec.OutputCapture.AddLine)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_logger.LogInformation("{execName} process id: {pid}", processSpec.ShortDisplayName(), process.Id);
|
||||||
|
await processState.Task;
|
||||||
|
}
|
||||||
|
|
||||||
exitCode = process.ExitCode;
|
exitCode = process.ExitCode;
|
||||||
}
|
}
|
||||||
|
|
||||||
LogResult(processSpec, exitCode);
|
if (!processSpec.IsOutputCaptured)
|
||||||
|
{
|
||||||
|
LogResult(processSpec, exitCode);
|
||||||
|
}
|
||||||
|
|
||||||
return exitCode;
|
return exitCode;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Process CreateProcess(ProcessSpec processSpec)
|
private Process CreateProcess(ProcessSpec processSpec)
|
||||||
{
|
{
|
||||||
var arguments = ArgumentEscaper.EscapeAndConcatenateArgArrayForProcessStart(processSpec.Arguments);
|
|
||||||
|
|
||||||
_logger.LogInformation("Running {execName} with the following arguments: {args}", processSpec.ShortDisplayName(), arguments);
|
|
||||||
|
|
||||||
var startInfo = new ProcessStartInfo
|
var startInfo = new ProcessStartInfo
|
||||||
{
|
{
|
||||||
FileName = processSpec.Executable,
|
FileName = processSpec.Executable,
|
||||||
Arguments = arguments,
|
Arguments = ArgumentEscaper.EscapeAndConcatenate(processSpec.Arguments),
|
||||||
UseShellExecute = false,
|
UseShellExecute = false,
|
||||||
WorkingDirectory = processSpec.WorkingDirectory
|
WorkingDirectory = processSpec.WorkingDirectory,
|
||||||
|
RedirectStandardOutput = processSpec.IsOutputCaptured,
|
||||||
|
RedirectStandardError = processSpec.IsOutputCaptured,
|
||||||
};
|
};
|
||||||
var process = new Process
|
var process = new Process
|
||||||
{
|
{
|
||||||
|
|
@ -81,6 +94,15 @@ namespace Microsoft.DotNet.Watcher.Internal
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static async Task ConsumeStreamAsync(StreamReader reader, Action<string> consume)
|
||||||
|
{
|
||||||
|
string line;
|
||||||
|
while ((line = await reader.ReadLineAsync().ConfigureAwait(false)) != null)
|
||||||
|
{
|
||||||
|
consume?.Invoke(line);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private class ProcessState : IDisposable
|
private class ProcessState : IDisposable
|
||||||
{
|
{
|
||||||
private readonly Process _process;
|
private readonly Process _process;
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@
|
||||||
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
using Microsoft.DotNet.Watcher.Internal;
|
||||||
|
|
||||||
namespace Microsoft.DotNet.Watcher
|
namespace Microsoft.DotNet.Watcher
|
||||||
{
|
{
|
||||||
|
|
@ -11,8 +12,11 @@ namespace Microsoft.DotNet.Watcher
|
||||||
public string Executable { get; set; }
|
public string Executable { get; set; }
|
||||||
public string WorkingDirectory { get; set; }
|
public string WorkingDirectory { get; set; }
|
||||||
public IEnumerable<string> Arguments { get; set; }
|
public IEnumerable<string> Arguments { get; set; }
|
||||||
|
public OutputCapture OutputCapture { get; set; }
|
||||||
|
|
||||||
public string ShortDisplayName()
|
public string ShortDisplayName()
|
||||||
=> Path.GetFileNameWithoutExtension(Executable);
|
=> Path.GetFileNameWithoutExtension(Executable);
|
||||||
|
|
||||||
|
public bool IsOutputCaptured => OutputCapture != null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,12 +2,14 @@
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using System.Diagnostics;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.DotNet.Cli.Utils;
|
|
||||||
using Microsoft.DotNet.Watcher.Internal;
|
using Microsoft.DotNet.Watcher.Internal;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
using Microsoft.Extensions.Tools.Internal;
|
||||||
|
|
||||||
namespace Microsoft.DotNet.Watcher
|
namespace Microsoft.DotNet.Watcher
|
||||||
{
|
{
|
||||||
|
|
@ -33,7 +35,7 @@ namespace Microsoft.DotNet.Watcher
|
||||||
|
|
||||||
public static int Main(string[] args)
|
public static int Main(string[] args)
|
||||||
{
|
{
|
||||||
DebugHelper.HandleDebugSwitch(ref args);
|
HandleDebugSwitch(ref args);
|
||||||
|
|
||||||
using (CancellationTokenSource ctrlCTokenSource = new CancellationTokenSource())
|
using (CancellationTokenSource ctrlCTokenSource = new CancellationTokenSource())
|
||||||
{
|
{
|
||||||
|
|
@ -41,7 +43,7 @@ namespace Microsoft.DotNet.Watcher
|
||||||
{
|
{
|
||||||
if (!ctrlCTokenSource.IsCancellationRequested)
|
if (!ctrlCTokenSource.IsCancellationRequested)
|
||||||
{
|
{
|
||||||
Console.WriteLine($"[{LoggerName}] Shutdown requested. Press CTRL+C again to force exit.");
|
Console.WriteLine($"[{LoggerName}] Shutdown requested. Press Ctrl+C again to force exit.");
|
||||||
ev.Cancel = true;
|
ev.Cancel = true;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
|
@ -96,12 +98,22 @@ namespace Microsoft.DotNet.Watcher
|
||||||
var logger = loggerFactory.CreateLogger(LoggerName);
|
var logger = loggerFactory.CreateLogger(LoggerName);
|
||||||
|
|
||||||
// TODO multiple projects should be easy enough to add here
|
// TODO multiple projects should be easy enough to add here
|
||||||
var projectFile = MsBuildProjectFinder.FindMsBuildProject(_workingDir, options.Project);
|
string projectFile;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
projectFile = MsBuildProjectFinder.FindMsBuildProject(_workingDir, options.Project);
|
||||||
|
}
|
||||||
|
catch (FileNotFoundException ex)
|
||||||
|
{
|
||||||
|
_stderr.WriteLine(ex.Message.Bold().Red());
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
var fileSetFactory = new MsBuildFileSetFactory(logger, projectFile);
|
var fileSetFactory = new MsBuildFileSetFactory(logger, projectFile);
|
||||||
|
|
||||||
var processInfo = new ProcessSpec
|
var processInfo = new ProcessSpec
|
||||||
{
|
{
|
||||||
Executable = new Muxer().MuxerPath,
|
Executable = DotNetMuxer.MuxerPathOrDefault(),
|
||||||
WorkingDirectory = Path.GetDirectoryName(projectFile),
|
WorkingDirectory = Path.GetDirectoryName(projectFile),
|
||||||
Arguments = options.RemainingArguments
|
Arguments = options.RemainingArguments
|
||||||
};
|
};
|
||||||
|
|
@ -120,7 +132,7 @@ namespace Microsoft.DotNet.Watcher
|
||||||
}
|
}
|
||||||
|
|
||||||
bool globalVerbose;
|
bool globalVerbose;
|
||||||
bool.TryParse(Environment.GetEnvironmentVariable(CommandContext.Variables.Verbose), out globalVerbose);
|
bool.TryParse(Environment.GetEnvironmentVariable("DOTNET_CLI_CONTEXT_VERBOSE"), out globalVerbose);
|
||||||
|
|
||||||
if (options.IsVerbose // dotnet watch --verbose
|
if (options.IsVerbose // dotnet watch --verbose
|
||||||
|| globalVerbose) // dotnet --verbose watch
|
|| globalVerbose) // dotnet --verbose watch
|
||||||
|
|
@ -130,5 +142,17 @@ namespace Microsoft.DotNet.Watcher
|
||||||
|
|
||||||
return LogLevel.Information;
|
return LogLevel.Information;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Conditional("DEBUG")]
|
||||||
|
private static void HandleDebugSwitch(ref string[] args)
|
||||||
|
{
|
||||||
|
if (args.Length > 0 && string.Equals("--debug", args[0], StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
args = args.Skip(1).ToArray();
|
||||||
|
Console.WriteLine("Waiting for debugger to attach. Press ENTER to continue");
|
||||||
|
Console.WriteLine($"Process ID: {Process.GetCurrentProcess().Id}");
|
||||||
|
Console.ReadLine();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -32,7 +32,6 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"Microsoft.DotNet.Cli.Utils": "1.0.0-preview3-004056",
|
|
||||||
"Microsoft.Extensions.Logging": "1.0.0",
|
"Microsoft.Extensions.Logging": "1.0.0",
|
||||||
"Microsoft.Extensions.Logging.Console": "1.0.0",
|
"Microsoft.Extensions.Logging.Console": "1.0.0",
|
||||||
"Microsoft.Extensions.Process.Sources": {
|
"Microsoft.Extensions.Process.Sources": {
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,107 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace Microsoft.Extensions.Tools.Internal
|
||||||
|
{
|
||||||
|
public static class ArgumentEscaper
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Undo the processing which took place to create string[] args in Main, so that the next process will
|
||||||
|
/// receive the same string[] args.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// See https://blogs.msdn.microsoft.com/twistylittlepassagesallalike/2011/04/23/everyone-quotes-command-line-arguments-the-wrong-way/
|
||||||
|
/// </remarks>
|
||||||
|
/// <param name="args"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static string EscapeAndConcatenate(IEnumerable<string> args)
|
||||||
|
=> string.Join(" ", args.Select(EscapeSingleArg));
|
||||||
|
|
||||||
|
private static string EscapeSingleArg(string arg)
|
||||||
|
{
|
||||||
|
var sb = new StringBuilder();
|
||||||
|
|
||||||
|
var needsQuotes = ShouldSurroundWithQuotes(arg);
|
||||||
|
var isQuoted = needsQuotes || IsSurroundedWithQuotes(arg);
|
||||||
|
|
||||||
|
if (needsQuotes)
|
||||||
|
{
|
||||||
|
sb.Append('"');
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < arg.Length; ++i)
|
||||||
|
{
|
||||||
|
var backslashes = 0;
|
||||||
|
|
||||||
|
// Consume all backslashes
|
||||||
|
while (i < arg.Length && arg[i] == '\\')
|
||||||
|
{
|
||||||
|
backslashes++;
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (i == arg.Length && isQuoted)
|
||||||
|
{
|
||||||
|
// Escape any backslashes at the end of the arg when the argument is also quoted.
|
||||||
|
// This ensures the outside quote is interpreted as an argument delimiter
|
||||||
|
sb.Append('\\', 2 * backslashes);
|
||||||
|
}
|
||||||
|
else if (i == arg.Length)
|
||||||
|
{
|
||||||
|
// At then end of the arg, which isn't quoted,
|
||||||
|
// just add the backslashes, no need to escape
|
||||||
|
sb.Append('\\', backslashes);
|
||||||
|
}
|
||||||
|
else if (arg[i] == '"')
|
||||||
|
{
|
||||||
|
// Escape any preceding backslashes and the quote
|
||||||
|
sb.Append('\\', (2 * backslashes) + 1);
|
||||||
|
sb.Append('"');
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Output any consumed backslashes and the character
|
||||||
|
sb.Append('\\', backslashes);
|
||||||
|
sb.Append(arg[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (needsQuotes)
|
||||||
|
{
|
||||||
|
sb.Append('"');
|
||||||
|
}
|
||||||
|
|
||||||
|
return sb.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool ShouldSurroundWithQuotes(string argument)
|
||||||
|
{
|
||||||
|
// Don't quote already quoted strings
|
||||||
|
if (IsSurroundedWithQuotes(argument))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only quote if whitespace exists in the string
|
||||||
|
return ContainsWhitespace(argument);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool IsSurroundedWithQuotes(string argument)
|
||||||
|
{
|
||||||
|
if (argument.Length <= 1)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return argument[0] == '"' && argument[argument.Length - 1] == '"';
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool ContainsWhitespace(string argument)
|
||||||
|
=> argument.IndexOfAny(new [] { ' ', '\t', '\n' }) >= 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,56 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
|
namespace Microsoft.Extensions.Tools.Internal
|
||||||
|
{
|
||||||
|
public static class DotNetMuxer
|
||||||
|
{
|
||||||
|
private const string MuxerName = "dotnet";
|
||||||
|
|
||||||
|
static DotNetMuxer()
|
||||||
|
{
|
||||||
|
MuxerPath = TryFindMuxerPath();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string MuxerPath { get; }
|
||||||
|
|
||||||
|
public static string MuxerPathOrDefault()
|
||||||
|
=> MuxerPath ?? MuxerName;
|
||||||
|
|
||||||
|
private static string TryFindMuxerPath()
|
||||||
|
{
|
||||||
|
var fileName = MuxerName;
|
||||||
|
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||||
|
{
|
||||||
|
fileName += ".exe";
|
||||||
|
}
|
||||||
|
|
||||||
|
var fxDepsFile = AppContext.GetData("FX_DEPS_FILE") as string;
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(fxDepsFile))
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var muxerDir = new FileInfo(fxDepsFile) // Microsoft.NETCore.App.deps.json
|
||||||
|
.Directory? // (version)
|
||||||
|
.Parent? // Microsoft.NETCore.App
|
||||||
|
.Parent? // shared
|
||||||
|
.Parent; // DOTNET_HOME
|
||||||
|
|
||||||
|
if (muxerDir == null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var muxer = Path.Combine(muxerDir.FullName, fileName);
|
||||||
|
return File.Exists(muxer)
|
||||||
|
? muxer
|
||||||
|
: null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,35 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
namespace System
|
||||||
|
{
|
||||||
|
public static class StringExtensions
|
||||||
|
{
|
||||||
|
public static string Black(this string text)
|
||||||
|
=> "\x1B[30m" + text + "\x1B[39m";
|
||||||
|
|
||||||
|
public static string Red(this string text)
|
||||||
|
=> "\x1B[31m" + text + "\x1B[39m";
|
||||||
|
|
||||||
|
public static string Green(this string text)
|
||||||
|
=> "\x1B[32m" + text + "\x1B[39m";
|
||||||
|
|
||||||
|
public static string Yellow(this string text)
|
||||||
|
=> "\x1B[33m" + text + "\x1B[39m";
|
||||||
|
|
||||||
|
public static string Blue(this string text)
|
||||||
|
=> "\x1B[34m" + text + "\x1B[39m";
|
||||||
|
|
||||||
|
public static string Magenta(this string text)
|
||||||
|
=> "\x1B[35m" + text + "\x1B[39m";
|
||||||
|
|
||||||
|
public static string Cyan(this string text)
|
||||||
|
=> "\x1B[36m" + text + "\x1B[39m";
|
||||||
|
|
||||||
|
public static string White(this string text)
|
||||||
|
=> "\x1B[37m" + text + "\x1B[39m";
|
||||||
|
|
||||||
|
public static string Bold(this string text)
|
||||||
|
=> "\x1B[1m" + text + "\x1B[22m";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -11,6 +11,7 @@
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"dotnet-test-xunit": "2.2.0-preview2-build1029",
|
"dotnet-test-xunit": "2.2.0-preview2-build1029",
|
||||||
|
"Microsoft.DotNet.Cli.Utils": "1.0.0-preview3-004056",
|
||||||
"Microsoft.DotNet.InternalAbstractions": "1.0.0",
|
"Microsoft.DotNet.InternalAbstractions": "1.0.0",
|
||||||
"Microsoft.AspNetCore.Testing": "1.0.0",
|
"Microsoft.AspNetCore.Testing": "1.0.0",
|
||||||
"Microsoft.DotNet.Watcher.Tools": "1.0.0-*",
|
"Microsoft.DotNet.Watcher.Tools": "1.0.0-*",
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,23 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using Microsoft.Extensions.Tools.Internal;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace Microsoft.DotNet.Watcher.Tools.Tests
|
||||||
|
{
|
||||||
|
public class ArgumentEscaperTests
|
||||||
|
{
|
||||||
|
[Theory]
|
||||||
|
[InlineData(new[] { "one", "two", "three" }, "one two three")]
|
||||||
|
[InlineData(new[] { "line1\nline2", "word1\tword2" }, "\"line1\nline2\" \"word1\tword2\"")]
|
||||||
|
[InlineData(new[] { "with spaces" }, "\"with spaces\"")]
|
||||||
|
[InlineData(new[] { @"with\backslash" }, @"with\backslash")]
|
||||||
|
[InlineData(new[] { @"""quotedwith\backslash""" }, @"\""quotedwith\backslash\""")]
|
||||||
|
[InlineData(new[] { @"C:\Users\" }, @"C:\Users\")]
|
||||||
|
[InlineData(new[] { @"C:\Program Files\dotnet\" }, @"""C:\Program Files\dotnet\\""")]
|
||||||
|
[InlineData(new[] { @"backslash\""preceedingquote" }, @"backslash\\\""preceedingquote")]
|
||||||
|
public void EscapesArguments(string[] args, string expected)
|
||||||
|
=> Assert.Equal(expected, ArgumentEscaper.EscapeAndConcatenate(args));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -6,7 +6,7 @@ using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
namespace Microsoft.DotNet.Watcher.Tools.FunctionalTests
|
namespace Microsoft.DotNet.Watcher.Tools.Tests
|
||||||
{
|
{
|
||||||
public class CommandLineOptionsTests
|
public class CommandLineOptionsTests
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -264,7 +264,9 @@ namespace Microsoft.DotNetWatcher.Tools.Tests
|
||||||
|
|
||||||
var fileset = await GetFileSet(filesetFactory);
|
var fileset = await GetFileSet(filesetFactory);
|
||||||
|
|
||||||
_logger.LogInformation(output.Current.GetAllLines("Sink output: "));
|
_logger.LogInformation(string.Join(
|
||||||
|
Environment.NewLine,
|
||||||
|
output.Current.Lines.Select(l => "Sink output: " + l)));
|
||||||
|
|
||||||
var includedProjects = new[] { "A", "B", "C", "D", "E", "F", "G" };
|
var includedProjects = new[] { "A", "B", "C", "D", "E", "F", "G" };
|
||||||
AssertEx.EqualFileList(
|
AssertEx.EqualFileList(
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue