From 0ba2e1feb4937ad0c496747534c2d15c79b08ac4 Mon Sep 17 00:00:00 2001 From: Mike Lorbetske Date: Thu, 25 May 2017 18:47:59 -0700 Subject: [PATCH] Initial add of launchSettings.json support --- .../LaunchSettings/ILaunchSettingsProvider.cs | 13 +++ .../LaunchSettings/LaunchSettingsManager.cs | 96 +++++++++++++++++++ .../ProjectLaunchSettingsProvider.cs | 57 +++++++++++ .../commands/dotnet-run/LocalizableStrings.cs | 10 ++ src/dotnet/commands/dotnet-run/RunCommand.cs | 34 +++++++ .../commands/dotnet-run/RunCommandParser.cs | 10 ++ 6 files changed, 220 insertions(+) create mode 100644 src/dotnet/commands/dotnet-run/LaunchSettings/ILaunchSettingsProvider.cs create mode 100644 src/dotnet/commands/dotnet-run/LaunchSettings/LaunchSettingsManager.cs create mode 100644 src/dotnet/commands/dotnet-run/LaunchSettings/ProjectLaunchSettingsProvider.cs diff --git a/src/dotnet/commands/dotnet-run/LaunchSettings/ILaunchSettingsProvider.cs b/src/dotnet/commands/dotnet-run/LaunchSettings/ILaunchSettingsProvider.cs new file mode 100644 index 000000000..b860aa545 --- /dev/null +++ b/src/dotnet/commands/dotnet-run/LaunchSettings/ILaunchSettingsProvider.cs @@ -0,0 +1,13 @@ +using Microsoft.DotNet.Cli.Utils; +using Newtonsoft.Json.Linq; + +namespace Microsoft.DotNet.Tools.Run.LaunchSettings +{ + public interface ILaunchSettingsProvider + { + string CommandName { get; } + + bool TryApplySettings(JObject document, JObject model, ref ICommand command, out string runAfterLaunch); + } + +} diff --git a/src/dotnet/commands/dotnet-run/LaunchSettings/LaunchSettingsManager.cs b/src/dotnet/commands/dotnet-run/LaunchSettings/LaunchSettingsManager.cs new file mode 100644 index 000000000..64fba9446 --- /dev/null +++ b/src/dotnet/commands/dotnet-run/LaunchSettings/LaunchSettingsManager.cs @@ -0,0 +1,96 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Microsoft.DotNet.Cli.Utils; +using Newtonsoft.Json.Linq; + +namespace Microsoft.DotNet.Tools.Run.LaunchSettings +{ + internal class LaunchSettingsManager + { + private const string ProfilesKey = "profiles"; + private const string CommandNameKey = "commandName"; + private const string DefaultProfileCommandName = "Project"; + private static IReadOnlyDictionary _providers; + + static LaunchSettingsManager() + { + _providers = new Dictionary + { + { ProjectLaunchSettingsProvider.CommandNameValue, new ProjectLaunchSettingsProvider() } + }; + } + + public static bool TryApplyLaunchSettings(string launchSettingsJsonContents, ref ICommand command, out string runAfterLaunch, string profileName = null) + { + try + { + var model = JObject.Parse(launchSettingsJsonContents); + var profilesObject = model[ProfilesKey] as JObject; + + if (profilesObject == null) + { + runAfterLaunch = null; + return false; + } + + JObject profileObject; + if (profileName == null) + { + profileObject = profilesObject + .Properties() + .FirstOrDefault(IsDefaultProfileType)?.Value as JObject; + } + else + { + profileObject = profilesObject[profileName] as JObject; + } + + if (profileObject == null) + { + foreach (var prop in profilesObject.Properties()) + { + var profile = prop.Value as JObject; + + if (profile != null) + { + var cmdName = profile[CommandNameKey]?.Value(); + if (_providers.ContainsKey(cmdName)) + { + profileObject = profile; + break; + } + } + } + } + + var commandName = profileObject?[CommandNameKey]?.Value(); + + if (profileObject == null || !TryLocateHandler(commandName, out ILaunchSettingsProvider provider)) + { + runAfterLaunch = null; + return false; + } + + return provider.TryApplySettings(model, profileObject, ref command, out runAfterLaunch); + } + catch + { + runAfterLaunch = null; + return false; + } + } + + private static bool TryLocateHandler(string commandName, out ILaunchSettingsProvider provider) + { + return _providers.TryGetValue(commandName, out provider); + } + + private static bool IsDefaultProfileType(JProperty profileProperty) + { + JObject profile = profileProperty.Value as JObject; + var commandName = profile?[CommandNameKey]?.Value(); + return string.Equals(commandName, DefaultProfileCommandName, StringComparison.Ordinal); + } + } +} diff --git a/src/dotnet/commands/dotnet-run/LaunchSettings/ProjectLaunchSettingsProvider.cs b/src/dotnet/commands/dotnet-run/LaunchSettings/ProjectLaunchSettingsProvider.cs new file mode 100644 index 000000000..905c4925a --- /dev/null +++ b/src/dotnet/commands/dotnet-run/LaunchSettings/ProjectLaunchSettingsProvider.cs @@ -0,0 +1,57 @@ +using System; +using System.Collections.Generic; +using Microsoft.DotNet.Cli.Utils; +using Newtonsoft.Json.Linq; + +namespace Microsoft.DotNet.Tools.Run.LaunchSettings +{ + public class ProjectLaunchSettingsProvider : ILaunchSettingsProvider + { + public static readonly string CommandNameValue = "Project"; + + public string CommandName => CommandNameValue; + + public bool TryApplySettings(JObject document, JObject model, ref ICommand command, out string runAfterLaunch) + { + try + { + var config = model.ToObject(); + + //For now, ignore everything but the environment variables section + + foreach (var entry in config.EnvironmentVariables) + { + string value = Environment.ExpandEnvironmentVariables(entry.Value); + //NOTE: MSBuild variables are not expanded like they are in VS + command.EnvironmentVariable(entry.Key, value); + } + + runAfterLaunch = null; + return true; + } + catch + { + runAfterLaunch = null; + return false; + } + } + + private class ProjectLaunchSettingsModel + { + public ProjectLaunchSettingsModel() + { + EnvironmentVariables = new Dictionary(); + } + + public string CommandLineArgs { get; set; } + + public bool LaunchBrowser { get; set; } + + public string LaunchUrl { get; set; } + + public string ApplicationUrl { get; set; } + + public Dictionary EnvironmentVariables { get; } + } + } +} diff --git a/src/dotnet/commands/dotnet-run/LocalizableStrings.cs b/src/dotnet/commands/dotnet-run/LocalizableStrings.cs index 4cdfd5217..5664f8b11 100644 --- a/src/dotnet/commands/dotnet-run/LocalizableStrings.cs +++ b/src/dotnet/commands/dotnet-run/LocalizableStrings.cs @@ -17,6 +17,10 @@ namespace Microsoft.DotNet.Tools.Run public const string CommandOptionProjectDescription = "The path to the project file to run (defaults to the current directory if there is only one project)."; + public const string CommandOptionLaunchProfileDescription = "The name of the launch profile (if any) to use when launching the application."; + + public const string CommandOptionNoLaunchProfileDescription = "Do not attempt to use launchSettings.json to configure the application."; + public const string RunCommandException = "The build failed. Please fix the build errors and run again."; public const string RunCommandExceptionUnableToRunSpecifyFramework = "Unable to run your project\nYour project targets multiple frameworks. Please specify which framework to run using '{0}'."; @@ -28,5 +32,11 @@ namespace Microsoft.DotNet.Tools.Run public const string RunCommandExceptionMultipleProjects = "Specify which project file to use because {0} contains more than one project file."; public const string RunCommandAdditionalArgsHelpText = "Arguments passed to the application that is being run."; + + public const string RunCommandExceptionCouldNotLocateALaunchSettingsFile = "The specified launch profile could not be located."; + + public const string RunCommandExceptionCouldNotApplyLaunchSettings = "The launch profile \"{0}\" could not be applied."; + + public const string DefaultLaunchProfileDisplayName = "(Default)"; } } diff --git a/src/dotnet/commands/dotnet-run/RunCommand.cs b/src/dotnet/commands/dotnet-run/RunCommand.cs index 5a60765ee..7b7d198e5 100644 --- a/src/dotnet/commands/dotnet-run/RunCommand.cs +++ b/src/dotnet/commands/dotnet-run/RunCommand.cs @@ -8,6 +8,7 @@ using System.Linq; using Microsoft.Build.Evaluation; using Microsoft.DotNet.Cli.Utils; using Microsoft.DotNet.Tools.MSBuild; +using Microsoft.DotNet.Tools.Run.LaunchSettings; namespace Microsoft.DotNet.Tools.Run { @@ -22,6 +23,10 @@ namespace Microsoft.DotNet.Tools.Run private List _args; private bool ShouldBuild => !NoBuild; + public string LaunchProfile { get; private set; } + public bool NoLaunchProfile { get; private set; } + + public int Start() { Initialize(); @@ -33,6 +38,27 @@ namespace Microsoft.DotNet.Tools.Run ICommand runCommand = GetRunCommand(); + if (!NoLaunchProfile) + { + var buildPathContainer = File.Exists(Project) ? Path.GetDirectoryName(Project) : Project; + var launchSettingsPath = Path.Combine(buildPathContainer, "Properties", "launchSettings.json"); + if (File.Exists(launchSettingsPath)) + { + var launchSettingsFileContents = File.ReadAllText(launchSettingsPath); + if (!LaunchSettingsManager.TryApplyLaunchSettings(launchSettingsFileContents, ref runCommand, out string runAfterLaunch, LaunchProfile)) + { + string profileName = string.IsNullOrEmpty(LaunchProfile) ? LocalizableStrings.DefaultLaunchProfileDisplayName : LaunchProfile; + //Error that the launch profile couldn't be applied + Reporter.Error.WriteLine(string.Format(LocalizableStrings.RunCommandExceptionCouldNotApplyLaunchSettings, profileName)); + } + } + else if (!string.IsNullOrEmpty(LaunchProfile)) + { + //Error that the launch profile couldn't be found + Reporter.Error.WriteLine(LocalizableStrings.RunCommandExceptionCouldNotLocateALaunchSettingsFile); + } + } + return runCommand .Execute() .ExitCode; @@ -42,12 +68,16 @@ namespace Microsoft.DotNet.Tools.Run string framework, bool noBuild, string project, + string launchProfile, + bool noLaunchProfile, IReadOnlyCollection args) { Configuration = configuration; Framework = framework; NoBuild = noBuild; Project = project; + LaunchProfile = launchProfile; + NoLaunchProfile = noLaunchProfile; Args = args; } @@ -55,6 +85,8 @@ namespace Microsoft.DotNet.Tools.Run string framework = null, bool? noBuild = null, string project = null, + string launchProfile = null, + bool? noLaunchProfile = null, IReadOnlyCollection args = null) { return new RunCommand( @@ -62,6 +94,8 @@ namespace Microsoft.DotNet.Tools.Run framework ?? this.Framework, noBuild ?? this.NoBuild, project ?? this.Project, + launchProfile ?? this.LaunchProfile, + noLaunchProfile ?? this.NoLaunchProfile, args ?? this.Args ); } diff --git a/src/dotnet/commands/dotnet-run/RunCommandParser.cs b/src/dotnet/commands/dotnet-run/RunCommandParser.cs index 54f439d79..ec0839c88 100644 --- a/src/dotnet/commands/dotnet-run/RunCommandParser.cs +++ b/src/dotnet/commands/dotnet-run/RunCommandParser.cs @@ -21,6 +21,8 @@ namespace Microsoft.DotNet.Cli framework: o.SingleArgumentOrDefault("--framework"), noBuild: o.HasOption("--no-build"), project: o.SingleArgumentOrDefault("--project"), + launchProfile: o.SingleArgumentOrDefault("--launch-profile"), + noLaunchProfile: o.HasOption("--no-launch-profile"), args: o.Arguments )), options: new[] @@ -32,6 +34,14 @@ namespace Microsoft.DotNet.Cli "-p|--project", LocalizableStrings.CommandOptionProjectDescription, Accept.ExactlyOneArgument()), + Create.Option( + "--launch-profile", + LocalizableStrings.CommandOptionLaunchProfileDescription, + Accept.ExactlyOneArgument()), + Create.Option( + "--no-launch-profile", + LocalizableStrings.CommandOptionNoLaunchProfileDescription, + Accept.NoArguments()), Create.Option( "--no-build", LocalizableStrings.CommandOptionNoBuildDescription,