Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -201,7 +201,12 @@ public static Option<bool> CreateUseCurrentRuntimeOption(string description) =>
{
Description = description,
HelpName = CommandDefinitionStrings.ConfigurationArgumentName,
IsDynamic = true
IsDynamic = true,
DefaultValueFactory = _ =>
{
string? configuration = Environment.GetEnvironmentVariable("Configuration");

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What if I have the following setup?

  1. Configuration env variable set to Release.
  2. Solution with two projects:
    • ProjectA: Typical project like the template console app.
    • ProjectB: It has explicit <Configuration>Debug</Configuration>

Are we introducing a behavior change for ProjectB before vs after this change?

@baronfel baronfel Jul 24, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

B never used Configuration in that case I believe - the environment variable would already override, because MSBuild read it from the env var.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you both. Resolving the thread

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@baronfel I tested on this csproj:

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net11.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
    <Configuration>Debug</Configuration>
  </PropertyGroup>

</Project>

On current SDK without this PR:

image

It looks like Configuration from csproj is winning (Note that I'm building csproj right away, the behavior is likely to be different when dealing with solution).

The change in this PR will make the same scenario use Release instead of Debug.

return string.IsNullOrWhiteSpace(configuration) ? null : configuration;
}
}.ForwardAsSingle(o => $"--property:Configuration={o}");

public static Option<string> CreateVersionSuffixOption() =>
Expand Down Expand Up @@ -380,4 +385,3 @@ public static void ValidateSelfContainedOptions(bool hasSelfContainedOption, boo
Arity = ArgumentArity.Zero
};
}

Original file line number Diff line number Diff line change
Expand Up @@ -253,8 +253,10 @@ string GetSymbolDefaultValue(Symbol symbol)

var isSingleArgument = defaultArguments.Length == 1;
var argumentDefaultValues = defaultArguments
.Select(argument => GetArgumentDefaultValue(symbol, argument, isSingleArgument, context));
return $"[{string.Join(", ", argumentDefaultValues)}]";
.Select(argument => GetArgumentDefaultValue(symbol, argument, isSingleArgument, context))
.Where(value => !string.IsNullOrWhiteSpace(value))
.ToArray();
return argumentDefaultValues.Length == 0 ? "" : $"[{string.Join(", ", argumentDefaultValues)}]";
}
}

Expand Down
4 changes: 2 additions & 2 deletions test/dotnet.Tests/CliSchemaTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ public CliSchemaTests()
],
"helpName": "CONFIGURATION",
"valueType": "System.String",
"hasDefaultValue": false,
"hasDefaultValue": true,
"arity": {
"minimum": 1,
"maximum": 1
Expand Down Expand Up @@ -799,7 +799,7 @@ public CliSchemaTests()
],
"helpName": "CONFIGURATION",
"valueType": "System.String",
"hasDefaultValue": false,
"hasDefaultValue": true,
"arity": {
"minimum": 1,
"maximum": 1
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,21 @@ public void RunWithSolutionPathWithFailingTests_ShouldReturnExitCodeAtLeastOneTe
result.ExitCode.Should().Be(ExitCodes.AtLeastOneTestFailed);
}

[TestMethod]
public void RunWithSolutionPath_ShouldUseConfigurationEnvironmentVariable()
{
TestAsset testInstance = TestAssetsManager.CopyTestAsset("MultiTestProjectSolutionWithTests", Guid.NewGuid().ToString()).WithSource();

CommandResult result = new DotnetTestCommand(Log, disableNewOutput: false)
.WithWorkingDirectory(testInstance.Path)
.WithEnvironmentVariable("Configuration", TestingConstants.Release)
.Execute("--solution", "MultiTestProjectSolutionWithTests.sln");

Assert.MatchesRegex(RegexPatternHelper.GenerateProjectRegexPattern("TestProject", TestingConstants.Failed, true, TestingConstants.Release), result.StdOut);
Assert.MatchesRegex(RegexPatternHelper.GenerateProjectRegexPattern("OtherTestProject", TestingConstants.Passed, true, TestingConstants.Release), result.StdOut);
result.ExitCode.Should().Be(ExitCodes.AtLeastOneTestFailed);
}

[TestMethod, CombinatorialData]
public void RunWithSolutionFilterPathWithFailingTests_ShouldReturnExitCodeGenericFailure(
[CombinatorialValues(TestingConstants.Debug, TestingConstants.Release)] string configuration,
Expand Down
75 changes: 75 additions & 0 deletions test/dotnet.Tests/ParserTests/CommonOptionsTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,87 @@

using System.CommandLine;
using Microsoft.DotNet.Cli;
using Microsoft.DotNet.Cli.CommandLine;

namespace Microsoft.DotNet.Tests.ParserTests;

[TestClass]
public class CommonOptionsTests
{
[TestMethod]
public void ConfigurationDefaultsToEnvironmentVariable()
{
string? originalConfiguration = Environment.GetEnvironmentVariable("Configuration");

try
{
Environment.SetEnvironmentVariable("Configuration", "EnvironmentConfiguration");
var command = new RootCommand();
var option = CommonOptions.CreateConfigurationOption("Configuration");
command.Options.Add(option);

var result = command.Parse([]);

result.GetValue(option).Should().Be("EnvironmentConfiguration");
result.OptionValuesToBeForwarded(command).Should().ContainSingle()
.Which.Should().Be("--property:Configuration=EnvironmentConfiguration");
}
finally
{
Environment.SetEnvironmentVariable("Configuration", originalConfiguration);
}
}

[TestMethod]
public void ExplicitConfigurationOverridesEnvironmentVariable()
{
string? originalConfiguration = Environment.GetEnvironmentVariable("Configuration");

try
{
Environment.SetEnvironmentVariable("Configuration", "EnvironmentConfiguration");
var command = new RootCommand();
var option = CommonOptions.CreateConfigurationOption("Configuration");
command.Options.Add(option);

var result = command.Parse(["--configuration", "ExplicitConfiguration"]);

result.GetValue(option).Should().Be("ExplicitConfiguration");
result.OptionValuesToBeForwarded(command).Should().ContainSingle()
.Which.Should().Be("--property:Configuration=ExplicitConfiguration");
}
finally
{
Environment.SetEnvironmentVariable("Configuration", originalConfiguration);
}
}

[TestMethod]
[DataRow("")]
[DataRow(" ")]
[DataRow("\t")]
public void EmptyOrWhitespaceConfigurationEnvironmentVariableIsIgnored(string configuration)
{
string? originalConfiguration = Environment.GetEnvironmentVariable("Configuration");

try
{
Environment.SetEnvironmentVariable("Configuration", configuration);
var command = new RootCommand();
var option = CommonOptions.CreateConfigurationOption("Configuration");
command.Options.Add(option);

var result = command.Parse([]);

result.GetValue(option).Should().BeNull();
result.OptionValuesToBeForwarded(command).Should().BeEmpty();
}
finally
{
Environment.SetEnvironmentVariable("Configuration", originalConfiguration);
}
}

[TestMethod]
public void Duplicates()
{
Expand Down
Loading