Skip to content

Commit

Permalink
Add DOTNET_LAUNCH_PROFILE environment when invoking dotnet run (#35029)
Browse files Browse the repository at this point in the history
  • Loading branch information
mitchdenny authored Sep 6, 2023
2 parents 9e835bc + 9bbf274 commit 479401d
Show file tree
Hide file tree
Showing 16 changed files with 229 additions and 15 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<Project Sdk="Microsoft.NET.Sdk" ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), testAsset.props))\testAsset.props" />

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>$(CurrentTargetFramework)</TargetFramework>
<RuntimeIdentifiers>$(LatestRuntimeIdentifiers)</RuntimeIdentifiers>
</PropertyGroup>
</Project>
Original file line number Diff line number Diff line change
@@ -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 value = Environment.GetEnvironmentVariable("DOTNET_LAUNCH_PROFILE");
Console.WriteLine($"DOTNET_LAUNCH_PROFILE=<<<{value}>>>");
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
{
// This comment is to verify proper comment handling by dotnet run. Do not remove.
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:49850/",
"sslPort": 0
}
},
"profiles": {
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"First": {
"commandName": "Project",
"environmentVariables": {
}
},
"Second": {
"commandName": "Project",
"environmentVariables": {
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ public static void Main(string[] args)
{
Console.WriteLine("Hello world");
Console.WriteLine($"MyCoolEnvironmentVariableKey={Environment.GetEnvironmentVariable("MyCoolEnvironmentVariableKey")}");
Console.WriteLine($"DOTNET_LAUNCH_PROFILE={Environment.GetEnvironmentVariable("DOTNET_LAUNCH_PROFILE")}");
if (args.Length > 0)
{
Console.WriteLine(args[0]);
Expand Down
1 change: 1 addition & 0 deletions src/Assets/TestProjects/WatchKitchenSink/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ static void Main(string[] args)
// Process ID is insufficient because PID's may be reused.
Console.WriteLine($"Process identifier = {Process.GetCurrentProcess().Id}, {Process.GetCurrentProcess().StartTime:hh:mm:ss.FF}");
Console.WriteLine("DOTNET_WATCH = " + Environment.GetEnvironmentVariable("DOTNET_WATCH"));
Console.WriteLine("DOTNET_LAUNCH_PROFILE = <<<" + Environment.GetEnvironmentVariable("DOTNET_LAUNCH_PROFILE") + ">>>");
Console.WriteLine("DOTNET_WATCH_ITERATION = " + Environment.GetEnvironmentVariable("DOTNET_WATCH_ITERATION"));

if (args.Length > 0 && args[0] == "wait")
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"profiles": {
"First": {
"commandName": "Project"
},
"Second": {
"commandName": "Project"
}
}
}
1 change: 1 addition & 0 deletions src/BuiltInTools/dotnet-watch/DotNetWatcher.cs
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ public async Task WatchAsync(DotNetWatchContext context, CancellationToken cance
context.RequiresMSBuildRevaluation = false;

processSpec.EnvironmentVariables["DOTNET_WATCH_ITERATION"] = (context.Iteration + 1).ToString(CultureInfo.InvariantCulture);
processSpec.EnvironmentVariables["DOTNET_LAUNCH_PROFILE"] = context.LaunchSettingsProfile?.LaunchProfileName ?? string.Empty;

var fileSet = context.FileSet;
if (fileSet == null)
Expand Down
1 change: 1 addition & 0 deletions src/BuiltInTools/dotnet-watch/HotReloadDotNetWatcher.cs
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ public async Task WatchAsync(DotNetWatchContext context, CancellationToken cance
}

processSpec.EnvironmentVariables["DOTNET_WATCH_ITERATION"] = (context.Iteration + 1).ToString(CultureInfo.InvariantCulture);
processSpec.EnvironmentVariables["DOTNET_LAUNCH_PROFILE"] = context.LaunchSettingsProfile?.LaunchProfileName ?? string.Empty;

var fileSet = context.FileSet;
if (fileSet == null)
Expand Down
12 changes: 9 additions & 3 deletions src/BuiltInTools/dotnet-watch/LaunchSettingsProfile.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,15 @@


using System.Text.Json;
using System.Text.Json.Serialization;
using Microsoft.Extensions.Tools.Internal;

namespace Microsoft.DotNet.Watcher.Tools
{
internal sealed class LaunchSettingsProfile
{
[JsonIgnore]
public string? LaunchProfileName { get; set; }
public string? ApplicationUrl { get; init; }
public string? CommandName { get; init; }
public bool LaunchBrowser { get; init; }
Expand Down Expand Up @@ -64,18 +67,21 @@ internal sealed class LaunchSettingsProfile
}

reporter.Verbose($"Found named launch profile '{launchProfileName}'.");
namedProfile.LaunchProfileName = launchProfileName;
return namedProfile;
}

private static LaunchSettingsProfile? ReadDefaultLaunchProfile(LaunchSettingsJson? launchSettings, IReporter reporter)
{
var defaultProfile = launchSettings?.Profiles?.FirstOrDefault(f => f.Value.CommandName == "Project").Value;

if (defaultProfile is null)
if (launchSettings is null || launchSettings.Profiles is null)
{
reporter.Verbose("Unable to find default launch profile.");
return null;
}

var defaultProfileKey = launchSettings.Profiles.FirstOrDefault(f => f.Value.CommandName == "Project").Key;
var defaultProfile = launchSettings.Profiles[defaultProfileKey];
defaultProfile.LaunchProfileName = defaultProfileKey;
return defaultProfile;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

#nullable enable

using System.Text.Json;

namespace Microsoft.DotNet.Tools.Run.LaunchSettings
{
internal interface ILaunchSettingsProvider
{
LaunchSettingsApplyResult TryGetLaunchSettings(JsonElement model);
LaunchSettingsApplyResult TryGetLaunchSettings(string? launchProfileName, JsonElement model);
}

}
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

#nullable enable

using System.Diagnostics.CodeAnalysis;
using System.Text.Json;
using Microsoft.DotNet.Cli.Utils;

Expand All @@ -21,7 +24,7 @@ static LaunchSettingsManager()
};
}

public static LaunchSettingsApplyResult TryApplyLaunchSettings(string launchSettingsJsonContents, string profileName = null)
public static LaunchSettingsApplyResult TryApplyLaunchSettings(string launchSettingsJsonContents, string? profileName = null)
{
try
{
Expand All @@ -40,12 +43,13 @@ public static LaunchSettingsApplyResult TryApplyLaunchSettings(string launchSett
return new LaunchSettingsApplyResult(false, LocalizableStrings.LaunchProfilesCollectionIsNotAJsonObject);
}

var selectedProfileName = profileName;
JsonElement profileObject;
if (string.IsNullOrEmpty(profileName))
{
profileObject = profilesObject
.EnumerateObject()
.FirstOrDefault(IsDefaultProfileType).Value;
var firstProfileProperty = profilesObject.EnumerateObject().FirstOrDefault(IsDefaultProfileType);
selectedProfileName = firstProfileProperty.Value.ValueKind == JsonValueKind.Object ? firstProfileProperty.Name : null;
profileObject = firstProfileProperty.Value;
}
else // Find a profile match for the given profileName
{
Expand Down Expand Up @@ -82,7 +86,7 @@ public static LaunchSettingsApplyResult TryApplyLaunchSettings(string launchSett
{
if (prop.Value.TryGetProperty(CommandNameKey, out var commandNameElement) && commandNameElement.ValueKind == JsonValueKind.String)
{
if (_providers.ContainsKey(commandNameElement.GetString()))
if (commandNameElement.GetString() is { } commandNameElementKey && _providers.ContainsKey(commandNameElementKey))
{
profileObject = prop.Value;
break;
Expand All @@ -103,13 +107,13 @@ public static LaunchSettingsApplyResult TryApplyLaunchSettings(string launchSett
return new LaunchSettingsApplyResult(false, LocalizableStrings.UsableLaunchProfileCannotBeLocated);
}

string commandName = finalCommandNameElement.GetString();
if (!TryLocateHandler(commandName, out ILaunchSettingsProvider provider))
string? commandName = finalCommandNameElement.GetString();
if (!TryLocateHandler(commandName, out ILaunchSettingsProvider? provider))
{
return new LaunchSettingsApplyResult(false, string.Format(LocalizableStrings.LaunchProfileHandlerCannotBeLocated, commandName));
}

return provider.TryGetLaunchSettings(profileObject);
return provider.TryGetLaunchSettings(selectedProfileName, profileObject);
}
}
catch (JsonException ex)
Expand All @@ -118,8 +122,14 @@ public static LaunchSettingsApplyResult TryApplyLaunchSettings(string launchSett
}
}

private static bool TryLocateHandler(string commandName, out ILaunchSettingsProvider provider)
private static bool TryLocateHandler(string? commandName, [NotNullWhen(true)]out ILaunchSettingsProvider? provider)
{
if (commandName == null)
{
provider = null;
return false;
}

return _providers.TryGetValue(commandName, out provider);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ namespace Microsoft.DotNet.Tools.Run.LaunchSettings
{
public class ProjectLaunchSettingsModel
{
public string LaunchProfileName { get; set; }

public string CommandLineArgs { get; set; }

public bool LaunchBrowser { get; set; }
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

#nullable enable

using System.Text.Json;

namespace Microsoft.DotNet.Tools.Run.LaunchSettings
Expand All @@ -11,9 +13,11 @@ internal class ProjectLaunchSettingsProvider : ILaunchSettingsProvider

public string CommandName => CommandNameValue;

public LaunchSettingsApplyResult TryGetLaunchSettings(JsonElement model)
public LaunchSettingsApplyResult TryGetLaunchSettings(string? launchProfileName, JsonElement model)
{
var config = new ProjectLaunchSettingsModel();
config.LaunchProfileName = launchProfileName;

foreach (var property in model.EnumerateObject())
{
if (string.Equals(property.Name, nameof(ProjectLaunchSettingsModel.CommandLineArgs), StringComparison.OrdinalIgnoreCase))
Expand Down Expand Up @@ -107,7 +111,7 @@ private static bool TryGetBooleanValue(JsonElement element, out bool value)
}
}

private static bool TryGetStringValue(JsonElement element, out string value)
private static bool TryGetStringValue(JsonElement element, out string? value)
{
switch (element.ValueKind)
{
Expand Down
2 changes: 2 additions & 0 deletions src/Cli/dotnet/commands/dotnet-run/RunCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,8 @@ public int Execute()
targetCommand.EnvironmentVariable("ASPNETCORE_URLS", launchSettings.ApplicationUrl);
}

targetCommand.EnvironmentVariable("DOTNET_LAUNCH_PROFILE", launchSettings.LaunchProfileName);

foreach (var entry in launchSettings.EnvironmentVariables)
{
string value = Environment.ExpandEnvironmentVariables(entry.Value);
Expand Down
80 changes: 80 additions & 0 deletions src/Tests/dotnet-run.Tests/GivenDotnetRunRunsCsProj.cs
Original file line number Diff line number Diff line change
Expand Up @@ -423,6 +423,86 @@ public void ItDefaultsToTheFirstUsableLaunchProfile()
cmd.StdErr.Should().BeEmpty();
}

[Fact]
public void ItSetsTheDotnetLaunchProfileEnvironmentVariableToDefaultLaunchProfileName()
{
var testAppName = "AppThatOutputsDotnetLaunchProfile";
var testInstance = _testAssetsManager.CopyTestAsset(testAppName)
.WithSource();

var testProjectDirectory = testInstance.Path;
var launchSettingsPath = Path.Combine(testProjectDirectory, "Properties", "launchSettings.json");

var cmd = new DotnetCommand(Log, "run")
.WithWorkingDirectory(testProjectDirectory)
.Execute();

cmd.Should().Pass()
.And.HaveStdOutContaining("DOTNET_LAUNCH_PROFILE=<<<First>>>");

cmd.StdErr.Should().BeEmpty();
}

[Fact]
public void ItSetsTheDotnetLaunchProfileEnvironmentVariableToSuppliedLaunchProfileName()
{
var testAppName = "AppThatOutputsDotnetLaunchProfile";
var testInstance = _testAssetsManager.CopyTestAsset(testAppName)
.WithSource();

var testProjectDirectory = testInstance.Path;
var launchSettingsPath = Path.Combine(testProjectDirectory, "Properties", "launchSettings.json");

var cmd = new DotnetCommand(Log, "run")
.WithWorkingDirectory(testProjectDirectory)
.Execute("--launch-profile", "Second");

cmd.Should().Pass()
.And.HaveStdOutContaining("DOTNET_LAUNCH_PROFILE=<<<Second>>>");

cmd.StdErr.Should().BeEmpty();
}

[Fact]
public void ItSetsTheDotnetLaunchProfileEnvironmentVariableToEmptyWhenInvalidProfileSpecified()
{
var testAppName = "AppThatOutputsDotnetLaunchProfile";
var testInstance = _testAssetsManager.CopyTestAsset(testAppName)
.WithSource();

var testProjectDirectory = testInstance.Path;
var launchSettingsPath = Path.Combine(testProjectDirectory, "Properties", "launchSettings.json");

var cmd = new DotnetCommand(Log, "run")
.WithWorkingDirectory(testProjectDirectory)
.Execute("--launch-profile", "DoesNotExist");

cmd.Should().Pass()
.And.HaveStdOutContaining("DOTNET_LAUNCH_PROFILE=<<<>>>");

cmd.StdErr.Should().Contain("DoesNotExist");
}

[Fact]
public void ItSetsTheDotnetLaunchProfileEnvironmentVariableToEmptyWhenNoLaunchProfileSwitchIsUsed()
{
var testAppName = "AppThatOutputsDotnetLaunchProfile";
var testInstance = _testAssetsManager.CopyTestAsset(testAppName)
.WithSource();

var testProjectDirectory = testInstance.Path;
var launchSettingsPath = Path.Combine(testProjectDirectory, "Properties", "launchSettings.json");

var cmd = new DotnetCommand(Log, "run")
.WithWorkingDirectory(testProjectDirectory)
.Execute("--no-launch-profile");

cmd.Should().Pass()
.And.HaveStdOutContaining("DOTNET_LAUNCH_PROFILE=<<<>>>");

cmd.StdErr.Should().BeEmpty();
}

[Fact]
public void ItPrintsUsingLaunchSettingsMessageWhenNotQuiet()
{
Expand Down
Loading

0 comments on commit 479401d

Please sign in to comment.