Skip to content

Commit ce50f27

Browse files
committed
initial impl and hookup
1 parent f6d7d84 commit ce50f27

23 files changed

+945
-1
lines changed

Directory.Packages.props

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,10 @@
4444
<PackageVersion Include="Microsoft.Extensions.DependencyModel" Version="$(MicrosoftExtensionsDependencyModelPackageVersion)" />
4545
<PackageVersion Include="Microsoft.Extensions.FileProviders.Abstractions" Version="$(MicrosoftExtensionsFileProvidersAbstractionsPackageVersion)" />
4646
<PackageVersion Include="Microsoft.Extensions.FileSystemGlobbing" Version="$(MicrosoftExtensionsFileSystemGlobbingPackageVersion)" />
47+
<PackageVersion Include="Microsoft.Extensions.Configuration" Version="$(MicrosoftExtensionsConfigurationPackageVersion)" />
48+
<PackageVersion Include="Microsoft.Extensions.Configuration.Abstractions" Version="$(MicrosoftExtensionsConfigurationAbstractionsPackageVersion)" />
49+
<PackageVersion Include="Microsoft.Extensions.Configuration.Binder" Version="$(MicrosoftExtensionsConfigurationBinderPackageVersion)" />
50+
<PackageVersion Include="Microsoft.Extensions.Configuration.Json" Version="$(MicrosoftExtensionsConfigurationJsonPackageVersion)" />
4751
<PackageVersion Include="Microsoft.Extensions.Logging" Version="$(MicrosoftExtensionsLoggingVersion)" />
4852
<PackageVersion Include="Microsoft.Extensions.Logging.Abstractions" Version="$(MicrosoftExtensionsLoggingAbstractionsVersion)" />
4953
<PackageVersion Include="Microsoft.Extensions.Logging.Console" Version="$(MicrosoftExtensionsLoggingConsoleVersion)" />

eng/Versions.props

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,10 @@
103103
<MicrosoftExtensionsFileSystemGlobbingPackageVersion>10.0.0-preview.7.25359.101</MicrosoftExtensionsFileSystemGlobbingPackageVersion>
104104
<MicrosoftExtensionsLoggingAbstractionsVersion>10.0.0-preview.7.25359.101</MicrosoftExtensionsLoggingAbstractionsVersion>
105105
<MicrosoftExtensionsLoggingConsoleVersion>10.0.0-preview.7.25359.101</MicrosoftExtensionsLoggingConsoleVersion>
106+
<MicrosoftExtensionsConfigurationPackageVersion>10.0.0-preview.7.25359.101</MicrosoftExtensionsConfigurationPackageVersion>
107+
<MicrosoftExtensionsConfigurationAbstractionsPackageVersion>10.0.0-preview.7.25359.101</MicrosoftExtensionsConfigurationAbstractionsPackageVersion>
108+
<MicrosoftExtensionsConfigurationBinderPackageVersion>10.0.0-preview.7.25359.101</MicrosoftExtensionsConfigurationBinderPackageVersion>
109+
<MicrosoftExtensionsConfigurationJsonPackageVersion>10.0.0-preview.7.25359.101</MicrosoftExtensionsConfigurationJsonPackageVersion>
106110
<MicrosoftExtensionsLoggingVersion>10.0.0-preview.7.25359.101</MicrosoftExtensionsLoggingVersion>
107111
<MicrosoftNETILLinkTasksPackageVersion>10.0.0-preview.7.25359.101</MicrosoftNETILLinkTasksPackageVersion>
108112
<MicrosoftExtensionsConfigurationIniVersion>10.0.0-preview.7.25359.101</MicrosoftExtensionsConfigurationIniVersion>

sdk.slnx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
<Project Path="src/Microsoft.Net.Sdk.Compilers.Toolset/Microsoft.Net.Sdk.Compilers.Toolset.csproj" />
2828
<Project Path="src/Microsoft.Win32.Msi/Microsoft.Win32.Msi.csproj" />
2929
<Project Path="src/System.CommandLine.StaticCompletions/System.CommandLine.StaticCompletions.csproj" />
30+
<Project Path="src/Microsoft.Extensions.Configuration.DotnetCli/Microsoft.Extensions.Configuration.DotnetCli.csproj" />
3031
</Folder>
3132
<Folder Name="/src/BlazorWasmSdk/">
3233
<Project Path="src/BlazorWasmSdk/Tasks/Microsoft.NET.Sdk.BlazorWebAssembly.Tasks.csproj" />
Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System;
5+
using System.Collections.Generic;
6+
using Microsoft.DotNet.Cli.Utils;
7+
using Microsoft.Extensions.Configuration.DotnetCli.Services;
8+
9+
namespace Microsoft.DotNet.Cli.Configuration
10+
{
11+
/// <summary>
12+
/// Bridge between the new configuration system and existing IEnvironmentProvider interface.
13+
/// Provides backward compatibility while enabling migration to the new configuration system.
14+
/// </summary>
15+
public class ConfigurationBasedEnvironmentProvider : IEnvironmentProvider
16+
{
17+
private readonly IDotNetConfigurationService _configurationService;
18+
private readonly IEnvironmentProvider _fallbackProvider;
19+
20+
// Reverse mapping from environment variable names to canonical keys
21+
private static readonly Dictionary<string, string> EnvironmentToCanonicalMappings = new()
22+
{
23+
["DOTNET_HOST_PATH"] = "RuntimeHost:HostPath",
24+
["DOTNET_CLI_TELEMETRY_OPTOUT"] = "CliUserExperience:TelemetryOptOut",
25+
["DOTNET_NOLOGO"] = "CliUserExperience:NoLogo",
26+
["DOTNET_CLI_FORCE_UTF8_ENCODING"] = "CliUserExperience:ForceUtf8Encoding",
27+
["DOTNET_CLI_UI_LANGUAGE"] = "CliUserExperience:UILanguage",
28+
["DOTNET_CLI_TELEMETRY_PROFILE"] = "CliUserExperience:TelemetryProfile",
29+
["DOTNET_CLI_PERF_LOG"] = "Development:PerfLogEnabled",
30+
["DOTNET_PERF_LOG_COUNT"] = "Development:PerfLogCount",
31+
["DOTNET_CLI_HOME"] = "Development:CliHome",
32+
["DOTNET_CLI_CONTEXT_VERBOSE"] = "Development:ContextVerbose",
33+
["DOTNET_MULTILEVEL_LOOKUP"] = "RuntimeHost:MultilevelLookup",
34+
["DOTNET_ROLL_FORWARD"] = "RuntimeHost:RollForward",
35+
["DOTNET_ROLL_FORWARD_ON_NO_CANDIDATE_FX"] = "RuntimeHost:RollForwardOnNoCandidateFx",
36+
["DOTNET_ROOT"] = "RuntimeHost:Root",
37+
["DOTNET_ROOT(x86)"] = "RuntimeHost:RootX86",
38+
["DOTNET_CLI_RUN_MSBUILD_OUTOFPROC"] = "Build:RunMSBuildOutOfProc",
39+
["DOTNET_CLI_USE_MSBUILD_SERVER"] = "Build:UseMSBuildServer",
40+
["DOTNET_CLI_CONFIGURE_MSBUILD_TERMINAL_LOGGER"] = "Build:ConfigureMSBuildTerminalLogger",
41+
["DOTNET_CLI_DISABLE_PUBLISH_AND_PACK_RELEASE"] = "Build:DisablePublishAndPackRelease",
42+
["DOTNET_CLI_LAZY_PUBLISH_AND_PACK_RELEASE_FOR_SOLUTIONS"] = "Build:LazyPublishAndPackReleaseForSolutions",
43+
["DOTNET_MSBUILD_SDK_RESOLVER_ENABLE_LOG"] = "SdkResolver:EnableLog",
44+
["DOTNET_MSBUILD_SDK_RESOLVER_SDKS_DIR"] = "SdkResolver:SdksDirectory",
45+
["DOTNET_MSBUILD_SDK_RESOLVER_SDKS_VER"] = "SdkResolver:SdksVersion",
46+
["DOTNET_MSBUILD_SDK_RESOLVER_CLI_DIR"] = "SdkResolver:CliDirectory",
47+
["DOTNET_CLI_WORKLOAD_UPDATE_NOTIFY_DISABLE"] = "Workload:UpdateNotifyDisable",
48+
["DOTNET_CLI_WORKLOAD_UPDATE_NOTIFY_INTERVAL_HOURS"] = "Workload:UpdateNotifyIntervalHours",
49+
["DOTNET_CLI_WORKLOAD_DISABLE_PACK_GROUPS"] = "Workload:DisablePackGroups",
50+
["DOTNET_SKIP_WORKLOAD_INTEGRITY_CHECK"] = "Workload:SkipIntegrityCheck",
51+
["DOTNETSDK_WORKLOAD_MANIFEST_ROOTS"] = "Workload:ManifestRoots",
52+
["DOTNETSDK_WORKLOAD_PACK_ROOTS"] = "Workload:PackRoots",
53+
["DOTNETSDK_WORKLOAD_MANIFEST_IGNORE_DEFAULT_ROOTS"] = "Workload:ManifestIgnoreDefaultRoots",
54+
["DOTNETSDK_ALLOW_TARGETING_PACK_CACHING"] = "Development:AllowTargetingPackCaching",
55+
["DOTNET_GENERATE_ASPNET_CERTIFICATE"] = "FirstTimeUse:GenerateAspNetCertificate",
56+
["DOTNET_ADD_GLOBAL_TOOLS_TO_PATH"] = "FirstTimeUse:AddGlobalToolsToPath",
57+
["DOTNET_SKIP_FIRST_TIME_EXPERIENCE"] = "FirstTimeUse:SkipFirstTimeExperience",
58+
["DOTNET_TOOLS_ALLOW_MANIFEST_IN_ROOT"] = "Tool:AllowManifestInRoot",
59+
};
60+
61+
public ConfigurationBasedEnvironmentProvider(
62+
IDotNetConfigurationService configurationService,
63+
IEnvironmentProvider? fallbackProvider = null)
64+
{
65+
_configurationService = configurationService ?? throw new ArgumentNullException(nameof(configurationService));
66+
_fallbackProvider = fallbackProvider ?? new EnvironmentProvider();
67+
}
68+
69+
public IEnumerable<string> ExecutableExtensions => _fallbackProvider.ExecutableExtensions;
70+
71+
public string? GetCommandPath(string commandName, params string[] extensions)
72+
{
73+
return _fallbackProvider.GetCommandPath(commandName, extensions);
74+
}
75+
76+
public string? GetCommandPathFromRootPath(string rootPath, string commandName, params string[] extensions)
77+
{
78+
return _fallbackProvider.GetCommandPathFromRootPath(rootPath, commandName, extensions);
79+
}
80+
81+
public string? GetCommandPathFromRootPath(string rootPath, string commandName, IEnumerable<string> extensions)
82+
{
83+
return _fallbackProvider.GetCommandPathFromRootPath(rootPath, commandName, extensions);
84+
}
85+
86+
public string? GetEnvironmentVariable(string name)
87+
{
88+
// First try to get from the new configuration system
89+
if (EnvironmentToCanonicalMappings.TryGetValue(name, out var canonicalKey))
90+
{
91+
var value = _configurationService.RawConfiguration[canonicalKey];
92+
if (!string.IsNullOrEmpty(value))
93+
{
94+
return value;
95+
}
96+
}
97+
98+
// Fall back to direct environment variable access
99+
return _fallbackProvider.GetEnvironmentVariable(name);
100+
}
101+
102+
public bool GetEnvironmentVariableAsBool(string name, bool defaultValue)
103+
{
104+
var value = GetEnvironmentVariable(name);
105+
if (string.IsNullOrEmpty(value))
106+
{
107+
return defaultValue;
108+
}
109+
110+
return value.ToLowerInvariant() switch
111+
{
112+
"true" or "1" or "yes" => true,
113+
"false" or "0" or "no" => false,
114+
_ => defaultValue
115+
};
116+
}
117+
118+
public int? GetEnvironmentVariableAsNullableInt(string name)
119+
{
120+
var value = GetEnvironmentVariable(name);
121+
if (string.IsNullOrEmpty(value))
122+
{
123+
return null;
124+
}
125+
126+
return int.TryParse(value, out var result) ? result : null;
127+
}
128+
129+
public string? GetEnvironmentVariable(string variable, EnvironmentVariableTarget target)
130+
{
131+
return _fallbackProvider.GetEnvironmentVariable(variable, target);
132+
}
133+
134+
public void SetEnvironmentVariable(string variable, string value, EnvironmentVariableTarget target)
135+
{
136+
_fallbackProvider.SetEnvironmentVariable(variable, value, target);
137+
}
138+
}
139+
}
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using Microsoft.Extensions.Configuration.DotnetCli.Services;
5+
using Microsoft.Extensions.Configuration.DotnetCli.Models;
6+
using Microsoft.Extensions.Configuration.DotnetCli.Providers;
7+
using Microsoft.Extensions.Configuration;
8+
using System.IO;
9+
10+
namespace Microsoft.DotNet.Cli.Configuration
11+
{
12+
/// <summary>
13+
/// Factory for creating and configuring the .NET CLI configuration service.
14+
/// This is the main entry point for the unified configuration system.
15+
/// </summary>
16+
public static class DotNetConfigurationFactory
17+
{
18+
/// <summary>
19+
/// Creates and configures the .NET CLI configuration service with default providers.
20+
/// This method follows the layered configuration approach: environment variables override global.json,
21+
/// and configuration is loaded lazily for performance.
22+
/// </summary>
23+
/// <param name="workingDirectory">The working directory to search for global.json files. Defaults to current directory.</param>
24+
/// <returns>A configured IDotNetConfigurationService instance</returns>
25+
public static IDotNetConfigurationService Create(string? workingDirectory = null)
26+
{
27+
workingDirectory ??= Directory.GetCurrentDirectory();
28+
29+
var configurationBuilder = new ConfigurationBuilder();
30+
31+
// Configuration sources are added in reverse precedence order
32+
// Last added has highest precedence
33+
34+
// 1. global.json (custom provider with key mapping) - lowest precedence
35+
configurationBuilder.Add(new GlobalJsonConfigurationSource(workingDirectory));
36+
37+
// 2. Environment variables (custom provider with key mapping) - highest precedence
38+
configurationBuilder.Add(new DotNetEnvironmentConfigurationSource());
39+
40+
var configuration = configurationBuilder.Build();
41+
42+
return new DotNetConfigurationService(configuration);
43+
}
44+
45+
/// <summary>
46+
/// Creates a minimal configuration service with only environment variables.
47+
/// This is useful for scenarios where global.json lookup is not needed or desirable,
48+
/// such as in performance-critical paths or testing scenarios.
49+
/// </summary>
50+
/// <returns>A minimal IDotNetConfigurationService instance</returns>
51+
public static IDotNetConfigurationService CreateMinimal()
52+
{
53+
var configurationBuilder = new ConfigurationBuilder();
54+
configurationBuilder.Add(new DotNetEnvironmentConfigurationSource());
55+
var configuration = configurationBuilder.Build();
56+
return new DotNetConfigurationService(configuration);
57+
}
58+
}
59+
}

src/Cli/dotnet/Program.cs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
using Microsoft.DotNet.Cli.CommandFactory.CommandResolution;
1111
using Microsoft.DotNet.Cli.Commands.Run;
1212
using Microsoft.DotNet.Cli.Commands.Workload;
13+
using Microsoft.DotNet.Cli.Configuration;
1314
using Microsoft.DotNet.Cli.Extensions;
1415
using Microsoft.DotNet.Cli.ShellShim;
1516
using Microsoft.DotNet.Cli.Telemetry;
@@ -177,7 +178,9 @@ internal static int ProcessArgs(string[] args, TimeSpan startupTime)
177178
{
178179
PerformanceLogEventSource.Log.FirstTimeConfigurationStart();
179180

180-
var environmentProvider = new EnvironmentProvider();
181+
// Initialize the new configuration-based environment provider
182+
var configurationService = DotNetConfigurationFactory.Create();
183+
var environmentProvider = new ConfigurationBasedEnvironmentProvider(configurationService);
181184

182185
bool generateAspNetCertificate = environmentProvider.GetEnvironmentVariableAsBool(EnvironmentVariableNames.DOTNET_GENERATE_ASPNET_CERTIFICATE, defaultValue: true);
183186
bool telemetryOptout = environmentProvider.GetEnvironmentVariableAsBool(EnvironmentVariableNames.TELEMETRY_OPTOUT, defaultValue: CompileOptions.TelemetryOptOutDefault);

src/Cli/dotnet/dotnet.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
<ProjectReference Include="../Microsoft.DotNet.Cli.Utils/Microsoft.DotNet.Cli.Utils.csproj" />
4444
<!-- override the Microsoft.TemplateEngine.Cli's dependency with the latest Microsoft.DotNet.TemplateLocator -->
4545
<ProjectReference Include="../../Microsoft.DotNet.TemplateLocator\Microsoft.DotNet.TemplateLocator.csproj" />
46+
<ProjectReference Include="../../Microsoft.Extensions.Configuration.DotnetCli/Microsoft.Extensions.Configuration.DotnetCli.csproj" />
4647
<ProjectReference Include="../../Resolvers\Microsoft.DotNet.NativeWrapper\Microsoft.DotNet.NativeWrapper.csproj" />
4748
<ProjectReference Include="../../Microsoft.Win32.Msi/Microsoft.Win32.Msi.csproj" />
4849
<ProjectReference Include="..\Microsoft.TemplateEngine.Cli\Microsoft.TemplateEngine.Cli.csproj" />
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<TargetFramework>$(SdkTargetFramework)</TargetFramework>
5+
<LangVersion>12.0</LangVersion>
6+
<Nullable>enable</Nullable>
7+
<EnableConfigurationBindingGenerator>true</EnableConfigurationBindingGenerator>
8+
</PropertyGroup>
9+
10+
<ItemGroup>
11+
<PackageReference Include="Microsoft.Extensions.Configuration" />
12+
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" />
13+
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" />
14+
<PackageReference Include="Microsoft.Extensions.Configuration.Json" />
15+
<PackageReference Include="Microsoft.Extensions.Configuration.Ini" />
16+
</ItemGroup>
17+
18+
</Project>
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
namespace Microsoft.Extensions.Configuration.DotnetCli.Models;
5+
6+
/// <summary>
7+
/// Configuration settings that control build system behavior.
8+
/// </summary>
9+
public sealed class BuildConfiguration
10+
{
11+
/// <summary>
12+
/// Gets or sets whether to run MSBuild out of process.
13+
/// Mapped from DOTNET_CLI_RUN_MSBUILD_OUTOFPROC environment variable.
14+
/// </summary>
15+
public bool RunMSBuildOutOfProc { get; set; } = false;
16+
17+
/// <summary>
18+
/// Gets or sets whether to use the MSBuild server for builds.
19+
/// Mapped from DOTNET_CLI_USE_MSBUILD_SERVER environment variable.
20+
/// </summary>
21+
public bool UseMSBuildServer { get; set; } = false;
22+
23+
/// <summary>
24+
/// Gets or sets the configuration for the MSBuild terminal logger.
25+
/// Mapped from DOTNET_CLI_CONFIGURE_MSBUILD_TERMINAL_LOGGER environment variable.
26+
/// </summary>
27+
public string? ConfigureMSBuildTerminalLogger { get; set; }
28+
29+
/// <summary>
30+
/// Gets or sets whether to disable publish and pack release configuration.
31+
/// Mapped from DOTNET_CLI_DISABLE_PUBLISH_AND_PACK_RELEASE environment variable.
32+
/// </summary>
33+
public bool DisablePublishAndPackRelease { get; set; } = false;
34+
35+
/// <summary>
36+
/// Gets or sets whether to enable lazy publish and pack release for solutions.
37+
/// Mapped from DOTNET_CLI_LAZY_PUBLISH_AND_PACK_RELEASE_FOR_SOLUTIONS environment variable.
38+
/// </summary>
39+
public bool LazyPublishAndPackReleaseForSolutions { get; set; } = false;
40+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
namespace Microsoft.Extensions.Configuration.DotnetCli.Models;
5+
6+
/// <summary>
7+
/// Configuration settings that control the CLI's user interface and interaction behavior.
8+
/// </summary>
9+
public sealed class CliUserExperienceConfiguration
10+
{
11+
/// <summary>
12+
/// Gets or sets whether telemetry collection is disabled.
13+
/// Mapped from DOTNET_CLI_TELEMETRY_OPTOUT environment variable.
14+
/// </summary>
15+
public bool TelemetryOptOut { get; set; } = false;
16+
17+
/// <summary>
18+
/// Gets or sets whether to suppress the .NET logo on startup.
19+
/// Mapped from DOTNET_NOLOGO environment variable.
20+
/// </summary>
21+
public bool NoLogo { get; set; } = false;
22+
23+
/// <summary>
24+
/// Gets or sets whether to force UTF-8 encoding for console output.
25+
/// Mapped from DOTNET_CLI_FORCE_UTF8_ENCODING environment variable.
26+
/// </summary>
27+
public bool ForceUtf8Encoding { get; set; } = false;
28+
29+
/// <summary>
30+
/// Gets or sets the UI language for the CLI.
31+
/// Mapped from DOTNET_CLI_UI_LANGUAGE environment variable.
32+
/// </summary>
33+
public string? UILanguage { get; set; }
34+
35+
/// <summary>
36+
/// Gets or sets the telemetry profile for data collection.
37+
/// Mapped from DOTNET_CLI_TELEMETRY_PROFILE environment variable.
38+
/// </summary>
39+
public string? TelemetryProfile { get; set; }
40+
}

0 commit comments

Comments
 (0)