Add --msbuildprojectextensionspath option to dotnet-watch

In the event someone wants to move the obj/ folder, MSBuild will not be able to locate dotnet-watch's generated targets. dotnet-watch cannot automatically find the obj folder (#244), so this command line switch allows users to point dotnet-watch to the right location.
This commit is contained in:
Nate McMaster 2017-06-01 19:48:45 -07:00
parent e8ee83d216
commit 34ea52068a
7 changed files with 80 additions and 16 deletions

View File

@ -12,6 +12,7 @@ namespace Microsoft.DotNet.Watcher
internal class CommandLineOptions
{
public string Project { get; private set; }
public string MSBuildProjectExtensionsPath { get; private set; }
public bool IsHelp { get; private set; }
public bool IsQuiet { get; private set; }
public bool IsVerbose { get; private set; }
@ -61,8 +62,16 @@ Examples:
};
app.HelpOption("-?|-h|--help");
var optProjects = app.Option("-p|--project", "The project to watch",
CommandOptionType.SingleValue); // TODO multiple shouldn't be too hard to support
// TODO multiple shouldn't be too hard to support
var optProjects = app.Option("-p|--project <PROJECT>", "The project to watch",
CommandOptionType.SingleValue);
var optMSBuildProjectExtensionsPath = app.Option("--msbuildprojectextensionspath <PATH>",
"The MSBuild project extensions path. Defaults to \"obj\".",
CommandOptionType.SingleValue);
// Hide from help text because this is an internal option that will hopefully go away when/if #244 is resolved.
optMSBuildProjectExtensionsPath.ShowInHelpText = false;
var optQuiet = app.Option("-q|--quiet", "Suppresses all output except warnings and errors",
CommandOptionType.NoValue);
var optVerbose = app.VerboseOption();
@ -92,6 +101,7 @@ Examples:
return new CommandLineOptions
{
Project = optProjects.Value(),
MSBuildProjectExtensionsPath = optMSBuildProjectExtensionsPath.Value(),
IsQuiet = optQuiet.HasValue(),
IsVerbose = optVerbose.HasValue(),
RemainingArguments = app.RemainingArguments,

View File

@ -21,19 +21,26 @@ namespace Microsoft.DotNet.Watcher.Internal
private const string WatchTargetsFileName = "DotNetWatchCommon.targets";
private readonly IReporter _reporter;
private readonly string _projectFile;
private readonly string _projectExtensionsPath;
private readonly string _watchTargetsDir;
private readonly OutputSink _outputSink;
private readonly ProcessRunner _processRunner;
private readonly bool _waitOnError;
public MsBuildFileSetFactory(IReporter reporter, string projectFile, bool waitOnError)
: this(reporter, projectFile, new OutputSink())
public MsBuildFileSetFactory(IReporter reporter,
string projectFile,
string msBuildProjectExtensionsPath,
bool waitOnError)
: this(reporter, projectFile, msBuildProjectExtensionsPath, new OutputSink())
{
_waitOnError = waitOnError;
}
// output sink is for testing
internal MsBuildFileSetFactory(IReporter reporter, string projectFile, OutputSink outputSink)
internal MsBuildFileSetFactory(IReporter reporter,
string projectFile,
string msBuildProjectExtensionsPath,
OutputSink outputSink)
{
Ensure.NotNull(reporter, nameof(reporter));
Ensure.NotNullOrEmpty(projectFile, nameof(projectFile));
@ -44,6 +51,11 @@ namespace Microsoft.DotNet.Watcher.Internal
_watchTargetsDir = FindWatchTargetsDir();
_outputSink = outputSink;
_processRunner = new ProcessRunner(reporter);
// default value for MSBuildProjectExtensionsPath is $(BaseIntermediateOutputPath), which defaults to 'obj/'.
_projectExtensionsPath = string.IsNullOrEmpty(msBuildProjectExtensionsPath)
? Path.Combine(Path.GetDirectoryName(_projectFile), "obj")
: msBuildProjectExtensionsPath;
}
internal List<string> BuildFlags { get; } = new List<string>
@ -153,11 +165,8 @@ namespace Microsoft.DotNet.Watcher.Internal
// Ensures file exists in $(MSBuildProjectExtensionsPath)/$(MSBuildProjectFile).dotnetwatch.targets
private void EnsureInitialized()
{
// default value for MSBuildProjectExtensionsPath.
var projectExtensionsPath = Path.Combine(Path.GetDirectoryName(_projectFile), "obj");
// see https://github.com/Microsoft/msbuild/blob/bf9b21cc7869b96ea2289ff31f6aaa5e1d525a26/src/XMakeTasks/Microsoft.Common.targets#L127
var projectExtensionFile = Path.Combine(projectExtensionsPath,
var projectExtensionFile = Path.Combine(_projectExtensionsPath,
Path.GetFileName(_projectFile) + ProjectExtensionFileExtension);
if (!File.Exists(projectExtensionFile))

View File

@ -81,12 +81,14 @@ namespace Microsoft.DotNet.Watcher
{
return await ListFilesAsync(_reporter,
options.Project,
options.MSBuildProjectExtensionsPath,
_cts.Token);
}
else
{
return await MainInternalAsync(_reporter,
options.Project,
options.MSBuildProjectExtensionsPath,
options.RemainingArguments,
_cts.Token);
}
@ -121,6 +123,7 @@ namespace Microsoft.DotNet.Watcher
private async Task<int> MainInternalAsync(
IReporter reporter,
string project,
string msbuildProjectExtensionsPath,
ICollection<string> args,
CancellationToken cancellationToken)
{
@ -136,7 +139,10 @@ namespace Microsoft.DotNet.Watcher
return 1;
}
var fileSetFactory = new MsBuildFileSetFactory(reporter, projectFile, waitOnError: true);
var fileSetFactory = new MsBuildFileSetFactory(reporter,
projectFile,
NormalizePath(msbuildProjectExtensionsPath),
waitOnError: true);
var processInfo = new ProcessSpec
{
Executable = DotNetMuxer.MuxerPathOrDefault(),
@ -157,6 +163,7 @@ namespace Microsoft.DotNet.Watcher
private async Task<int> ListFilesAsync(
IReporter reporter,
string project,
string msbuildProjectExtensionsPath,
CancellationToken cancellationToken)
{
// TODO multiple projects should be easy enough to add here
@ -171,7 +178,10 @@ namespace Microsoft.DotNet.Watcher
return 1;
}
var fileSetFactory = new MsBuildFileSetFactory(reporter, projectFile, waitOnError: false);
var fileSetFactory = new MsBuildFileSetFactory(reporter,
projectFile,
NormalizePath(msbuildProjectExtensionsPath),
waitOnError: false);
var files = await fileSetFactory.CreateAsync(cancellationToken);
if (files == null)
@ -190,6 +200,22 @@ namespace Microsoft.DotNet.Watcher
private static IReporter CreateReporter(bool verbose, bool quiet, IConsole console)
=> new PrefixConsoleReporter(console, verbose || CliContext.IsGlobalVerbose(), quiet);
private string NormalizePath(string path)
{
if (path == null || Path.IsPathRooted(path))
{
return path;
}
if (string.IsNullOrWhiteSpace(path))
{
return _workingDir;
}
return Path.Combine(_workingDir, path);
}
public void Dispose()
{
_console.CancelKeyPress -= OnCancelKeyPress;

View File

@ -2,6 +2,7 @@
// 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.Threading.Tasks;
using Xunit;
using Xunit.Abstractions;
@ -40,6 +41,11 @@ namespace Microsoft.DotNet.Watcher.Tools.FunctionalTests
: base("KitchenSink", logger)
{
}
protected override IEnumerable<string> GetDefaultArgs()
{
return new[] { "--msbuildprojectextensionspath", ".net/obj", "run", "--" };
}
}
}
}

View File

@ -96,7 +96,7 @@ namespace Microsoft.DotNet.Watcher.Tools.FunctionalTests
await PrepareAsync();
}
var args = new[] { "run", "--" }.Concat(arguments);
var args = GetDefaultArgs().Concat(arguments);
Start(args, name);
// Make this timeout long because it depends much on the MSBuild compilation speed.
@ -104,6 +104,11 @@ namespace Microsoft.DotNet.Watcher.Tools.FunctionalTests
await Process.GetOutputLineAsync(StartedMessage).TimeoutAfter(TimeSpan.FromMinutes(2));
}
protected virtual IEnumerable<string> GetDefaultArgs()
{
return new[] { "run", "--" };
}
public virtual void Dispose()
{
_logger?.WriteLine("Disposing WatchableApp");

View File

@ -1,8 +1,16 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project>
<PropertyGroup>
<BaseIntermediateOutputPath>.net/obj</BaseIntermediateOutputPath>
</PropertyGroup>
<Import Project="Sdk.props" Sdk="Microsoft.NET.Sdk" />
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp2.0</TargetFramework>
</PropertyGroup>
<Import Project="Sdk.targets" Sdk="Microsoft.NET.Sdk" />
</Project>

View File

@ -238,7 +238,7 @@ namespace Microsoft.DotNet.Watcher.Tools.Tests
graph.Find("A").WithProjectReference(graph.Find("W"), watch: false);
var output = new OutputSink();
var filesetFactory = new MsBuildFileSetFactory(_reporter, graph.GetOrCreate("A").Path, output)
var filesetFactory = new MsBuildFileSetFactory(_reporter, graph.GetOrCreate("A").Path, null, output)
{
// enables capturing markers to know which projects have been visited
BuildFlags = { "/p:_DotNetWatchTraceOutput=true" }
@ -280,7 +280,7 @@ namespace Microsoft.DotNet.Watcher.Tools.Tests
}
private Task<IFileSet> GetFileSet(TemporaryCSharpProject target)
=> GetFileSet(new MsBuildFileSetFactory(_reporter, target.Path, waitOnError: false));
=> GetFileSet(new MsBuildFileSetFactory(_reporter, target.Path, null, waitOnError: false));
private async Task<IFileSet> GetFileSet(MsBuildFileSetFactory filesetFactory)
{
@ -298,4 +298,4 @@ namespace Microsoft.DotNet.Watcher.Tools.Tests
_tempDir.Dispose();
}
}
}
}