diff --git a/src/Cli/Microsoft.DotNet.Cli.Utils/BuiltInCommand.cs b/src/Cli/Microsoft.DotNet.Cli.Utils/BuiltInCommand.cs index 279e86d30261..4c32f9f84cf9 100644 --- a/src/Cli/Microsoft.DotNet.Cli.Utils/BuiltInCommand.cs +++ b/src/Cli/Microsoft.DotNet.Cli.Utils/BuiltInCommand.cs @@ -19,6 +19,7 @@ public class BuiltInCommand : ICommand public string CommandName { get; } public string CommandArgs => string.Join(" ", _commandArgs); + public string CommandWorkingDirectory => _workingDirectory; public BuiltInCommand(string commandName, IEnumerable commandArgs, Func builtInCommand) : this(commandName, commandArgs, builtInCommand, new BuiltInCommandEnvironment()) diff --git a/src/Cli/Microsoft.DotNet.Cli.Utils/Command.cs b/src/Cli/Microsoft.DotNet.Cli.Utils/Command.cs index 2d22a0edd774..8d81d746df0c 100644 --- a/src/Cli/Microsoft.DotNet.Cli.Utils/Command.cs +++ b/src/Cli/Microsoft.DotNet.Cli.Utils/Command.cs @@ -182,6 +182,8 @@ public ICommand OnErrorLine(Action handler) public string CommandArgs => _process.StartInfo.Arguments; + public string CommandWorkingDirectory => _process.StartInfo.WorkingDirectory; + public ICommand SetCommandArgs(string commandArgs) { _process.StartInfo.Arguments = commandArgs; diff --git a/src/Cli/Microsoft.DotNet.Cli.Utils/ICommand.cs b/src/Cli/Microsoft.DotNet.Cli.Utils/ICommand.cs index 31fdaa008ef5..078b5cc4596b 100644 --- a/src/Cli/Microsoft.DotNet.Cli.Utils/ICommand.cs +++ b/src/Cli/Microsoft.DotNet.Cli.Utils/ICommand.cs @@ -28,5 +28,7 @@ public interface ICommand string CommandName { get; } string CommandArgs { get; } + + string CommandWorkingDirectory { get; } } } diff --git a/src/Cli/dotnet/commands/dotnet-run/LaunchSettings/ProjectLaunchSettingsModel.cs b/src/Cli/dotnet/commands/dotnet-run/LaunchSettings/ProjectLaunchSettingsModel.cs index 79edef71ded8..d2aaedb589fc 100644 --- a/src/Cli/dotnet/commands/dotnet-run/LaunchSettings/ProjectLaunchSettingsModel.cs +++ b/src/Cli/dotnet/commands/dotnet-run/LaunchSettings/ProjectLaunchSettingsModel.cs @@ -18,5 +18,7 @@ public class ProjectLaunchSettingsModel public string DotNetRunMessages { get; set; } public Dictionary EnvironmentVariables { get; } = new Dictionary(StringComparer.Ordinal); + + public string WorkingDirectory { get; set; } } } diff --git a/src/Cli/dotnet/commands/dotnet-run/LaunchSettings/ProjectLaunchSettingsProvider.cs b/src/Cli/dotnet/commands/dotnet-run/LaunchSettings/ProjectLaunchSettingsProvider.cs index 80c2035cc056..ebc271f02c6c 100644 --- a/src/Cli/dotnet/commands/dotnet-run/LaunchSettings/ProjectLaunchSettingsProvider.cs +++ b/src/Cli/dotnet/commands/dotnet-run/LaunchSettings/ProjectLaunchSettingsProvider.cs @@ -82,6 +82,15 @@ public LaunchSettingsApplyResult TryGetLaunchSettings(string? launchProfileName, } } } + else if (string.Equals(property.Name, nameof(ProjectLaunchSettingsModel.WorkingDirectory), StringComparison.OrdinalIgnoreCase)) + { + if (!TryGetStringValue(property.Value, out var workingDirectory)) + { + return new LaunchSettingsApplyResult(false, string.Format(LocalizableStrings.CouldNotConvertToString, property.Name)); + } + + config.WorkingDirectory = workingDirectory; + } } return new LaunchSettingsApplyResult(true, null, config); diff --git a/src/Cli/dotnet/commands/dotnet-run/RunCommand.cs b/src/Cli/dotnet/commands/dotnet-run/RunCommand.cs index e7b154267041..686ea1b393df 100644 --- a/src/Cli/dotnet/commands/dotnet-run/RunCommand.cs +++ b/src/Cli/dotnet/commands/dotnet-run/RunCommand.cs @@ -109,6 +109,10 @@ private ICommand ApplyLaunchSettingsProfileToCommand(ICommand targetCommand, Pro { targetCommand.SetCommandArgs(launchSettings.CommandLineArgs); } + if (launchSettings.WorkingDirectory != null) + { + targetCommand.WorkingDirectory(launchSettings.WorkingDirectory); + } } return targetCommand; } diff --git a/test/TestAssets/TestProjects/AppWithWorkingDirectoryInLaunchSettings/AppWithWorkingDirectoryInLaunchSettings.csproj b/test/TestAssets/TestProjects/AppWithWorkingDirectoryInLaunchSettings/AppWithWorkingDirectoryInLaunchSettings.csproj new file mode 100644 index 000000000000..9ba9b997ea9b --- /dev/null +++ b/test/TestAssets/TestProjects/AppWithWorkingDirectoryInLaunchSettings/AppWithWorkingDirectoryInLaunchSettings.csproj @@ -0,0 +1,9 @@ + + + + + Exe + $(CurrentTargetFramework) + $(LatestRuntimeIdentifiers) + + diff --git a/test/TestAssets/TestProjects/AppWithWorkingDirectoryInLaunchSettings/Program.cs b/test/TestAssets/TestProjects/AppWithWorkingDirectoryInLaunchSettings/Program.cs new file mode 100644 index 000000000000..b457a22255b3 --- /dev/null +++ b/test/TestAssets/TestProjects/AppWithWorkingDirectoryInLaunchSettings/Program.cs @@ -0,0 +1,16 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; + +namespace MSBuildTestApp +{ + public class Program + { + public static void Main(string[] args) + { + var message = Environment.CurrentDirectory; + Console.WriteLine(message); + } + } +} diff --git a/test/TestAssets/TestProjects/AppWithWorkingDirectoryInLaunchSettings/Properties/launchSettings.json b/test/TestAssets/TestProjects/AppWithWorkingDirectoryInLaunchSettings/Properties/launchSettings.json new file mode 100644 index 000000000000..a9aad1113118 --- /dev/null +++ b/test/TestAssets/TestProjects/AppWithWorkingDirectoryInLaunchSettings/Properties/launchSettings.json @@ -0,0 +1,11 @@ +{ + "profiles": { + "First":{ + "commandName": "Project" + }, + "Second":{ + "commandName": "Project", + "workingDirectory": "launchSubfolder" + } + } +} \ No newline at end of file diff --git a/test/dotnet-run.Tests/GivenDotnetRunBuildsCsProj.cs b/test/dotnet-run.Tests/GivenDotnetRunBuildsCsProj.cs index d083626b91ab..52197473838d 100644 --- a/test/dotnet-run.Tests/GivenDotnetRunBuildsCsProj.cs +++ b/test/dotnet-run.Tests/GivenDotnetRunBuildsCsProj.cs @@ -561,6 +561,53 @@ public void ItUsesTheValueOfAppUrlIfTheEnvVarIsNotSet() cmd.StdErr.Should().BeEmpty(); } + [Fact] + public void ItUsesTheValueOfWorkingDirectoryIfSet() + { + var testAppName = "AppWithWorkingDirectoryInLaunchSettings"; + var testInstance = _testAssetsManager.CopyTestAsset(testAppName) + .WithSource(); + + var testProjectDirectory = testInstance.Path; + + Directory.CreateDirectory(Path.Combine(testProjectDirectory, "launchSubfolder")); + + var cmd = new DotnetCommand(Log, "run") + .WithWorkingDirectory(testProjectDirectory) + .Execute("--launch-profile", "Second"); + + cmd.Should().Pass() + .And.HaveStdOutContaining("launchSubfolder"); + + cmd.StdErr.Should().BeEmpty(); + } + + [Fact] + public void ItPrefersTheValueOfWorkingDirectoryFromLaunchSettingsOverProjectRunWorkingDirectory() + { + var testAppName = "AppWithWorkingDirectoryInLaunchSettings"; + var testInstance = _testAssetsManager.CopyTestAsset(testAppName) + .WithSource() + .WithProjectChanges(p => { + var ns = p.Root.Name.Namespace; + var propertyGroup = p.Root.Elements(ns + "PropertyGroup").First(); + propertyGroup.Add(new XElement(ns + "RunWorkingDirectory", "expectThisSubfolderIsOverridden")); + }); + + var testProjectDirectory = testInstance.Path; + + Directory.CreateDirectory(Path.Combine(testProjectDirectory, "launchSubfolder")); + + var cmd = new DotnetCommand(Log, "run") + .WithWorkingDirectory(testProjectDirectory) + .Execute("--launch-profile", "Second"); + + cmd.Should().Pass() + .And.HaveStdOutContaining("launchSubfolder"); + + cmd.StdErr.Should().BeEmpty(); + } + [Fact] public void ItGivesAnErrorWhenTheLaunchProfileNotFound() {