diff --git a/documentation/specs/multithreading/msbuild-task-enlightenment.md b/documentation/specs/multithreading/msbuild-task-enlightenment.md new file mode 100644 index 00000000000..fa00392e1e5 --- /dev/null +++ b/documentation/specs/multithreading/msbuild-task-enlightenment.md @@ -0,0 +1,26 @@ +# MSBuild Repository Task Enlightenment Guidelines + +This document provides specific guidance for enlightening tasks within the MSBuild repository. + +## Acceptable Changes in Task Behavior During Enlightenment + +**The fundamental principle is to preserve task behavior and outputs for all input options.** + +### Permissible Changes +Modifications to error messaging or failure location are acceptable when a task fails with a different but reasonable error at an alternative execution point, provided that **no significant side effects** (such as disk modifications or output changes) occur between the original and new failure points. + +### Changes Requiring Change Waves +Modifications that alter the success or failure outcome of a task for given inputs require careful evaluation. Such changes are permissible only when implemented behind a **change wave** and when the affected scenarios represent **obscure or edge use cases**. + +## Immutable Environment Variables in MSBuild + +Certain MSBuild tasks (such as `GetFrameworkPath`) use internal infrastructure that depends on environment variables for resolution. These results are cached in-process for reuse by both tasks and internal MSBuild components. MSBuild assumes that these variables remain constant throughout the build process. + +While multiprocess mode cannot prevent tasks from modifying these variables, multithreaded mode enables MSBuild to enforce protection of environment variables that must remain immutable. Attempts to modify these protected variables will result in an `InvalidOperationException`. + +With this protection in place, we will allow MSBuild tasks to continue using internal infrastructure directly without requiring the TaskEnvironment API. + +MSBuild protects environment variables in these categories: + +1. **Variables with MSBuild-specific prefixes** (e.g. ones used in Traits) +2. **Framework and SDK location variables** (e.g., `COMPLUS_INSTALL_ROOT`, `COMPLUS_VERSION`, `ReferenceAssemblyRoot`, `ProgramW6432`, etc) diff --git a/src/Build.UnitTests/BackEnd/BuildManager_Tests.cs b/src/Build.UnitTests/BackEnd/BuildManager_Tests.cs index 4cbc6f23c14..fdaf18eca91 100644 --- a/src/Build.UnitTests/BackEnd/BuildManager_Tests.cs +++ b/src/Build.UnitTests/BackEnd/BuildManager_Tests.cs @@ -3491,7 +3491,7 @@ public void WarningsAreTreatedAsErrorsButTargetsStillSucceed() [Fact] public void DotnetHostPathDirectoryWarning() { - _env.SetEnvironmentVariable(Constants.DotnetHostPathEnvVarName, Path.GetTempPath()); + _env.SetEnvironmentVariable(EnvironmentVariablesNames.DotnetHostPath, Path.GetTempPath()); BuildRequestData data = GetBuildRequestData(CleanupFileContents(@"")); _buildManager.Build(_parameters, data); @@ -3506,7 +3506,7 @@ public void DotnetHostPathDirectoryWarning() public void DotnetHostPathFileNoWarning() { TransientTestFile tempFile = _env.CreateFile("dotnet.exe", ""); - _env.SetEnvironmentVariable(Constants.DotnetHostPathEnvVarName, tempFile.Path); + _env.SetEnvironmentVariable(EnvironmentVariablesNames.DotnetHostPath, tempFile.Path); BuildRequestData data = GetBuildRequestData(CleanupFileContents(@"")); _buildManager.Build(_parameters, data); @@ -3520,7 +3520,7 @@ public void DotnetHostPathFileNoWarning() [Fact] public void DotnetHostPathNotSetNoWarning() { - _env.SetEnvironmentVariable(Constants.DotnetHostPathEnvVarName, null); + _env.SetEnvironmentVariable(EnvironmentVariablesNames.DotnetHostPath, null); BuildRequestData data = GetBuildRequestData(CleanupFileContents(@"")); _buildManager.Build(_parameters, data); diff --git a/src/Build.UnitTests/BackEnd/TaskEnvironment_Tests.cs b/src/Build.UnitTests/BackEnd/TaskEnvironment_Tests.cs index 4a9d6126ced..3c21f7ef7f2 100644 --- a/src/Build.UnitTests/BackEnd/TaskEnvironment_Tests.cs +++ b/src/Build.UnitTests/BackEnd/TaskEnvironment_Tests.cs @@ -17,6 +17,8 @@ public class TaskEnvironment_Tests { private const string StubEnvironmentName = "Stub"; private const string MultithreadedEnvironmentName = "Multithreaded"; + private const string ImmutableTestVar = "MSBUILDTESTVAR"; + private const string TestValue = "value"; public static TheoryData EnvironmentTypes => new TheoryData @@ -32,7 +34,13 @@ private static TaskEnvironment CreateTaskEnvironment(string environmentType) return environmentType switch { StubEnvironmentName => TaskEnvironmentHelper.CreateForTest(), - MultithreadedEnvironmentName => new TaskEnvironment(new MultiThreadedTaskEnvironmentDriver(GetResolvedTempPath())), + MultithreadedEnvironmentName => new TaskEnvironment(new MultiThreadedTaskEnvironmentDriver( + GetResolvedTempPath(), + new Dictionary + { + ["env_var_1"] = "env_var_1_value", + ["env_var_2"] = "env_var_2_value" + })), _ => throw new ArgumentException($"Unknown environment type: {environmentType}") }; } @@ -86,7 +94,7 @@ private static string GetResolvedTempPath() public void TaskEnvironment_SetAndGetEnvironmentVariable_ShouldWork(string environmentType) { var taskEnvironment = CreateTaskEnvironment(environmentType); - string testVarName = $"MSBUILD_TEST_VAR_{environmentType}_{Guid.NewGuid():N}"; + string testVarName = $"TEST_ENV_VAR_{environmentType}_{Guid.NewGuid():N}"; string testVarValue = $"test_value_{environmentType}"; try @@ -112,7 +120,7 @@ public void TaskEnvironment_SetAndGetEnvironmentVariable_ShouldWork(string envir public void TaskEnvironment_SetEnvironmentVariableToNull_ShouldRemoveVariable(string environmentType) { var taskEnvironment = CreateTaskEnvironment(environmentType); - string testVarName = $"MSBUILD_REMOVE_TEST_{environmentType}_{Guid.NewGuid():N}"; + string testVarName = $"TEST_ENV_VAR_REMOVE_{environmentType}_{Guid.NewGuid():N}"; string testVarValue = "value_to_remove"; try @@ -138,7 +146,7 @@ public void TaskEnvironment_SetEnvironmentVariableToNull_ShouldRemoveVariable(st public void TaskEnvironment_SetEnvironment_ShouldReplaceAllVariables(string environmentType) { var taskEnvironment = CreateTaskEnvironment(environmentType); - string prefix = $"MSBUILD_SET_ENV_TEST_{environmentType}_{Guid.NewGuid():N}"; + string prefix = $"TEST_ENV_VAR_SET_{environmentType}_{Guid.NewGuid():N}"; string var1Name = $"{prefix}_VAR1"; string var2Name = $"{prefix}_VAR2"; string var3Name = $"{prefix}_VAR3"; @@ -265,7 +273,7 @@ public void TaskEnvironment_GetProcessStartInfo_ShouldConfigureCorrectly(string { var taskEnvironment = CreateTaskEnvironment(environmentType); string testDirectory = GetResolvedTempPath(); - string testVarName = $"MSBUILD_PROCESS_TEST_{environmentType}_{Guid.NewGuid():N}"; + string testVarName = $"TEST_ENV_VAR_PROCESS_{environmentType}_{Guid.NewGuid():N}"; string testVarValue = "process_test_value"; string originalDirectory = Directory.GetCurrentDirectory(); @@ -303,7 +311,7 @@ public void TaskEnvironment_GetProcessStartInfo_ShouldConfigureCorrectly(string [Fact] public void TaskEnvironment_StubEnvironment_ShouldAffectSystemEnvironment() { - string testVarName = $"MSBUILD_STUB_ISOLATION_TEST_{Guid.NewGuid():N}"; + string testVarName = $"TEST_ENV_VAR_STUB_ISOLATION_{Guid.NewGuid():N}"; string testVarValue = "stub_test_value"; var stubEnvironment = TaskEnvironmentHelper.CreateForTest(); @@ -333,7 +341,7 @@ public void TaskEnvironment_StubEnvironment_ShouldAffectSystemEnvironment() [Fact] public void TaskEnvironment_MultithreadedEnvironment_ShouldBeIsolatedFromSystem() { - string testVarName = $"MSBUILD_MULTITHREADED_ISOLATION_TEST_{Guid.NewGuid():N}"; + string testVarName = $"TEST_ENV_VAR_MULTITHREADED_ISOLATION_{Guid.NewGuid():N}"; string testVarValue = "multithreaded_test_value"; using var driver = new MultiThreadedTaskEnvironmentDriver( @@ -384,5 +392,107 @@ public void TaskEnvironment_GetAbsolutePath_WithInvalidPathChars_ShouldNotThrow( DisposeTaskEnvironment(taskEnvironment); } } + + [Fact] + public void MultiThreadedTaskEnvironmentDriver_SetEnvironmentVariable_ThrowsOnCaseChangeOfImmutableValue() + { + // Changing just the case of an immutable variable's value should throw because + // value comparison must be case-sensitive (StringComparison.Ordinal). + // If we incorrectly used case-insensitive comparison, this would not throw. + using var driver = new MultiThreadedTaskEnvironmentDriver( + GetResolvedTempPath(), + new Dictionary(StringComparer.OrdinalIgnoreCase) + { + [ImmutableTestVar] = TestValue + }); + var taskEnvironment = new TaskEnvironment(driver); + + // Changing "value" to "VALUE" is a modification (case-sensitive) and should throw + Should.Throw(() => + taskEnvironment.SetEnvironmentVariable(ImmutableTestVar, TestValue.ToUpperInvariant())); + } + + [Fact] + public void MultiThreadedTaskEnvironmentDriver_SetEnvironmentVariable_ThrowsOnImmutableVariable() + { + // MSBuild-prefixed variables are immutable and should throw when adding + using var driver = new MultiThreadedTaskEnvironmentDriver( + GetResolvedTempPath(), + new Dictionary(StringComparer.OrdinalIgnoreCase)); + var taskEnvironment = new TaskEnvironment(driver); + + Should.Throw(() => + taskEnvironment.SetEnvironmentVariable(ImmutableTestVar, TestValue)); + } + + [Fact] + public void MultiThreadedTaskEnvironmentDriver_SetEnvironmentVariable_AllowsSettingSameValue() + { + // Setting an immutable variable to its current value should not throw + using var driver = new MultiThreadedTaskEnvironmentDriver( + GetResolvedTempPath(), + new Dictionary(StringComparer.OrdinalIgnoreCase) + { + [ImmutableTestVar] = TestValue + }); + var taskEnvironment = new TaskEnvironment(driver); + + Should.NotThrow(() => + taskEnvironment.SetEnvironmentVariable(ImmutableTestVar, TestValue)); + } + + [Fact] + public void MultiThreadedTaskEnvironmentDriver_SetEnvironment_ThrowsOnImmutableVariableRemoval() + { + // Removing an immutable variable via SetEnvironment should throw + using var driver = new MultiThreadedTaskEnvironmentDriver( + GetResolvedTempPath(), + new Dictionary(StringComparer.OrdinalIgnoreCase) + { + [ImmutableTestVar] = TestValue + }); + var taskEnvironment = new TaskEnvironment(driver); + + // New environment without the immutable variable - this is a removal + var newEnvironment = new Dictionary(StringComparer.OrdinalIgnoreCase); + + Should.Throw(() => + taskEnvironment.SetEnvironment(newEnvironment)); + } + + [Fact] + public void MultiThreadedTaskEnvironmentDriver_SetEnvironmentVariable_ThrowsOnImmutableVariableRemoval() + { + // Removing an immutable variable by setting to null should throw + using var driver = new MultiThreadedTaskEnvironmentDriver( + GetResolvedTempPath(), + new Dictionary(StringComparer.OrdinalIgnoreCase) + { + [ImmutableTestVar] = TestValue + }); + var taskEnvironment = new TaskEnvironment(driver); + + Should.Throw(() => + taskEnvironment.SetEnvironmentVariable(ImmutableTestVar, null)); + } + + [Fact] + public void MultiThreadedTaskEnvironmentDriver_EscapeHatch_DisablesImmutableVariableCheck() + { + // When the escape hatch is set, modifying immutable variables should not throw + using TestEnvironment env = TestEnvironment.Create(); + env.SetEnvironmentVariable("MSBUILDDISABLEIMMUTABLEENVIRONMENTVARIABLECHECK", "1"); + + using var driver = new MultiThreadedTaskEnvironmentDriver( + GetResolvedTempPath(), + new Dictionary(StringComparer.OrdinalIgnoreCase)); + var taskEnvironment = new TaskEnvironment(driver); + + // With escape hatch enabled, setting an MSBUILD-prefixed variable should not throw + Should.NotThrow(() => + taskEnvironment.SetEnvironmentVariable(ImmutableTestVar, TestValue)); + + taskEnvironment.GetEnvironmentVariable(ImmutableTestVar).ShouldBe(TestValue); + } } } diff --git a/src/Build.UnitTests/Evaluation/Evaluator_Tests.cs b/src/Build.UnitTests/Evaluation/Evaluator_Tests.cs index 7c289d90ef0..d9c2dc527c9 100644 --- a/src/Build.UnitTests/Evaluation/Evaluator_Tests.cs +++ b/src/Build.UnitTests/Evaluation/Evaluator_Tests.cs @@ -2626,7 +2626,7 @@ public void MSBuildExtensionsPath64Default() if (!String.IsNullOrEmpty(programFiles32)) { // only set in 32-bit windows on 64-bit machines - expected = Environment.GetEnvironmentVariable("ProgramW6432"); + expected = Environment.GetEnvironmentVariable(EnvironmentVariablesNames.ProgramW6432); if (string.IsNullOrEmpty(expected)) { diff --git a/src/Build/BackEnd/BuildManager/EnvironmentVariableValidator.cs b/src/Build/BackEnd/BuildManager/EnvironmentVariableValidator.cs index dbadbd40d93..39b726f81d4 100644 --- a/src/Build/BackEnd/BuildManager/EnvironmentVariableValidator.cs +++ b/src/Build/BackEnd/BuildManager/EnvironmentVariableValidator.cs @@ -6,7 +6,6 @@ using Microsoft.Build.Framework; using Microsoft.Build.Shared; using Microsoft.Build.Shared.FileSystem; -using Constants = Microsoft.Build.Framework.Constants; namespace Microsoft.Build.Execution { @@ -32,7 +31,7 @@ internal static void ValidateEnvironmentVariables(ILoggingService loggingService /// private static void ValidateDotnetHostPath(ILoggingService loggingService) { - string? dotnetHostPath = Environment.GetEnvironmentVariable(Constants.DotnetHostPathEnvVarName); + string? dotnetHostPath = Environment.GetEnvironmentVariable(EnvironmentVariablesNames.DotnetHostPath); if (string.IsNullOrEmpty(dotnetHostPath)) { return; diff --git a/src/Build/BackEnd/TaskExecutionHost/MultiThreadedTaskEnvironmentDriver.cs b/src/Build/BackEnd/TaskExecutionHost/MultiThreadedTaskEnvironmentDriver.cs index db7bc2413e5..ca8608f5ca9 100644 --- a/src/Build/BackEnd/TaskExecutionHost/MultiThreadedTaskEnvironmentDriver.cs +++ b/src/Build/BackEnd/TaskExecutionHost/MultiThreadedTaskEnvironmentDriver.cs @@ -7,6 +7,7 @@ using System.Diagnostics; using Microsoft.Build.Framework; using Microsoft.Build.Internal; +using Microsoft.Build.Shared; namespace Microsoft.Build.BackEnd { @@ -37,6 +38,21 @@ public MultiThreadedTaskEnvironmentDriver( ProjectDirectory = new AbsolutePath(currentDirectoryFullPath, ignoreRootedCheck: true); } + /// + /// Validates that the specified environment variable can be modified. + /// Throws if the variable is one that MSBuild assumes should remain constant. + /// + /// The name of the environment variable to check. + /// Thrown when attempting to modify an immutable environment variable. + private static void EnsureVariableCanBeModified(string name) + { + if (EnvironmentVariableClassifier.Instance.IsImmutable(name)) + { + throw new InvalidOperationException( + ResourceUtilities.FormatResourceStringStripCodeAndKeyword("TaskCannotModifyImmutableEnvironmentVariable", name)); + } + } + /// /// Initializes a new instance of the class /// with the specified working directory and environment variables from the current process. @@ -88,6 +104,16 @@ public IReadOnlyDictionary GetEnvironmentVariables() /// public void SetEnvironmentVariable(string name, string? value) { + if (!Traits.Instance.EscapeHatches.DisableImmutableEnvironmentVariableCheck) + { + // Only validate if we're actually changing the value + _environmentVariables.TryGetValue(name, out string? currentValue); + if (!string.Equals(currentValue, value, StringComparison.Ordinal)) + { + EnsureVariableCanBeModified(name); + } + } + if (value == null) { _environmentVariables.Remove(name); @@ -101,6 +127,30 @@ public void SetEnvironmentVariable(string name, string? value) /// public void SetEnvironment(IDictionary newEnvironment) { + if (!Traits.Instance.EscapeHatches.DisableImmutableEnvironmentVariableCheck) + { + // Check for variables being removed (exist in current but not in new environment) + foreach (string currentVar in _environmentVariables.Keys) + { + if (!newEnvironment.ContainsKey(currentVar)) + { + EnsureVariableCanBeModified(currentVar); + } + } + + // Check for variables being added or modified + foreach (KeyValuePair entry in newEnvironment) + { + _environmentVariables.TryGetValue(entry.Key, out string? currentValue); + + // Only validate if we're actually changing the value + if (!string.Equals(currentValue, entry.Value, StringComparison.Ordinal)) + { + EnsureVariableCanBeModified(entry.Key); + } + } + } + // Simply replace the entire environment dictionary _environmentVariables.Clear(); foreach (KeyValuePair entry in newEnvironment) diff --git a/src/Build/Evaluation/Evaluator.cs b/src/Build/Evaluation/Evaluator.cs index 371c8b56ff1..8ec66d5f625 100644 --- a/src/Build/Evaluation/Evaluator.cs +++ b/src/Build/Evaluation/Evaluator.cs @@ -1904,7 +1904,7 @@ static string EvaluateProperty(string value, IElementLocation location, string dotnetExe = Path.Combine(FileUtilities.GetFolderAbove(sdkResult.Path, 5), Constants.DotnetProcessName); if (FileSystems.Default.FileExists(dotnetExe)) { - _data.AddSdkResolvedEnvironmentVariable(Constants.DotnetHostPathEnvVarName, dotnetExe); + _data.AddSdkResolvedEnvironmentVariable(EnvironmentVariablesNames.DotnetHostPath, dotnetExe); } } diff --git a/src/Build/Instance/TaskFactories/AssemblyTaskFactory.cs b/src/Build/Instance/TaskFactories/AssemblyTaskFactory.cs index 7b080d2b3b5..d3930b7b26e 100644 --- a/src/Build/Instance/TaskFactories/AssemblyTaskFactory.cs +++ b/src/Build/Instance/TaskFactories/AssemblyTaskFactory.cs @@ -654,7 +654,7 @@ private static TaskHostParameters AddNetHostParamsIfNeeded( return currentParams; } - string dotnetHostPath = getProperty(Constants.DotnetHostPathEnvVarName)?.EvaluatedValue; + string dotnetHostPath = getProperty(EnvironmentVariablesNames.DotnetHostPath)?.EvaluatedValue; string netCoreSdkRoot = getProperty(Constants.NetCoreSdkRoot)?.EvaluatedValue?.TrimEnd('/', '\\'); // The NetCoreSdkRoot property got added with .NET 11, so for earlier SDKs we fall back to the RID graph path diff --git a/src/Build/Resources/Strings.resx b/src/Build/Resources/Strings.resx index c573bbdd54d..06a6cb0d1d1 100644 --- a/src/Build/Resources/Strings.resx +++ b/src/Build/Resources/Strings.resx @@ -145,6 +145,9 @@ The operation cannot be completed because EndBuild has already been called but existing submissions have not yet completed. + + Cannot modify environment variable '{0}': variable is immutable and must remain constant during the build. + Property '{0}' with value '{1}' expanded from the environment. diff --git a/src/Build/Resources/xlf/Strings.cs.xlf b/src/Build/Resources/xlf/Strings.cs.xlf index 043072c5530..c5ef95706b1 100644 --- a/src/Build/Resources/xlf/Strings.cs.xlf +++ b/src/Build/Resources/xlf/Strings.cs.xlf @@ -1002,6 +1002,11 @@ Chyby: {3} Sestavení úlohy bylo načteno z{0}, ale požadované umístění bylo{1}. + + Cannot modify environment variable '{0}': variable is immutable and must remain constant during the build. + Cannot modify environment variable '{0}': variable is immutable and must remain constant during the build. + + Task "{0}" released {1} cores and now holds {2} cores total. Úloha {0} uvolnila tento počet jader: {1}. Teď používá celkem tento počet jader: {2} diff --git a/src/Build/Resources/xlf/Strings.de.xlf b/src/Build/Resources/xlf/Strings.de.xlf index 13e8c4a1028..bc2ec0b9fbe 100644 --- a/src/Build/Resources/xlf/Strings.de.xlf +++ b/src/Build/Resources/xlf/Strings.de.xlf @@ -1002,6 +1002,11 @@ Fehler: {3} Die Aufgabenassembly wurde aus „{0}“ geladen, während der gewünschte Speicherort „{1}“ war. + + Cannot modify environment variable '{0}': variable is immutable and must remain constant during the build. + Cannot modify environment variable '{0}': variable is immutable and must remain constant during the build. + + Task "{0}" released {1} cores and now holds {2} cores total. Die Aufgabe "{0}" hat {1} Kerne freigegeben und belegt jetzt insgesamt {2} Kerne. diff --git a/src/Build/Resources/xlf/Strings.es.xlf b/src/Build/Resources/xlf/Strings.es.xlf index 03acc62388f..91856ff82fa 100644 --- a/src/Build/Resources/xlf/Strings.es.xlf +++ b/src/Build/Resources/xlf/Strings.es.xlf @@ -1002,6 +1002,11 @@ Errores: {3} El ensamblado de tarea se cargó desde "{0}" mientras que la ubicación deseada era "{1}". + + Cannot modify environment variable '{0}': variable is immutable and must remain constant during the build. + Cannot modify environment variable '{0}': variable is immutable and must remain constant during the build. + + Task "{0}" released {1} cores and now holds {2} cores total. La tarea "{0}" liberó {1} núcleos y ahora retiene un total de {2} núcleos. diff --git a/src/Build/Resources/xlf/Strings.fr.xlf b/src/Build/Resources/xlf/Strings.fr.xlf index 4887128eaca..f6373a41a3b 100644 --- a/src/Build/Resources/xlf/Strings.fr.xlf +++ b/src/Build/Resources/xlf/Strings.fr.xlf @@ -1002,6 +1002,11 @@ Erreurs : {3} L’assembly de tâche a été chargé à partir de « {0} » alors que l’emplacement souhaité était « {1} ». + + Cannot modify environment variable '{0}': variable is immutable and must remain constant during the build. + Cannot modify environment variable '{0}': variable is immutable and must remain constant during the build. + + Task "{0}" released {1} cores and now holds {2} cores total. La tâche "{0}" a libéré {1} cœur. Elle détient désormais {2} cœurs au total. diff --git a/src/Build/Resources/xlf/Strings.it.xlf b/src/Build/Resources/xlf/Strings.it.xlf index 6f3a39ae585..ae428e45eb1 100644 --- a/src/Build/Resources/xlf/Strings.it.xlf +++ b/src/Build/Resources/xlf/Strings.it.xlf @@ -1002,6 +1002,11 @@ Errori: {3} L'assembly attività è stato caricato da "{0}" mentre era "{1}" il percorso desiderato. + + Cannot modify environment variable '{0}': variable is immutable and must remain constant during the build. + Cannot modify environment variable '{0}': variable is immutable and must remain constant during the build. + + Task "{0}" released {1} cores and now holds {2} cores total. L'attività "{0}" ha rilasciato {1} core e ora contiene {2} core in totale. diff --git a/src/Build/Resources/xlf/Strings.ja.xlf b/src/Build/Resources/xlf/Strings.ja.xlf index d8ce6bf4357..a2d04c0b79e 100644 --- a/src/Build/Resources/xlf/Strings.ja.xlf +++ b/src/Build/Resources/xlf/Strings.ja.xlf @@ -1002,6 +1002,11 @@ Errors: {3} タスク アセンブリは '{0}' から読み込まれましたが、必要な場所は '{1}' でした。 + + Cannot modify environment variable '{0}': variable is immutable and must remain constant during the build. + Cannot modify environment variable '{0}': variable is immutable and must remain constant during the build. + + Task "{0}" released {1} cores and now holds {2} cores total. タスク "{0}" では、{1} 個のコアを解放したため、現在合計 {2} 個のコアを保持しています。 diff --git a/src/Build/Resources/xlf/Strings.ko.xlf b/src/Build/Resources/xlf/Strings.ko.xlf index 7c369855cd3..6a704fc0cab 100644 --- a/src/Build/Resources/xlf/Strings.ko.xlf +++ b/src/Build/Resources/xlf/Strings.ko.xlf @@ -1002,6 +1002,11 @@ Errors: {3} 원하는 위치가 '{1}'인 동안 '{0}'에서 작업 어셈블리를 로드했습니다. + + Cannot modify environment variable '{0}': variable is immutable and must remain constant during the build. + Cannot modify environment variable '{0}': variable is immutable and must remain constant during the build. + + Task "{0}" released {1} cores and now holds {2} cores total. "{0}" 작업에서 코어 {1}개를 해제했고 지금 총 {2}개의 코어를 보유하고 있습니다. diff --git a/src/Build/Resources/xlf/Strings.pl.xlf b/src/Build/Resources/xlf/Strings.pl.xlf index bd540f5ff51..beffb2aa003 100644 --- a/src/Build/Resources/xlf/Strings.pl.xlf +++ b/src/Build/Resources/xlf/Strings.pl.xlf @@ -1002,6 +1002,11 @@ Błędy: {3} Zestaw zadania został załadowany z lokalizacji „{0}”, gdy żądana lokalizacja to „{1}”. + + Cannot modify environment variable '{0}': variable is immutable and must remain constant during the build. + Cannot modify environment variable '{0}': variable is immutable and must remain constant during the build. + + Task "{0}" released {1} cores and now holds {2} cores total. Zadanie „{0}” zwolniło rdzenie ({1}) i teraz jego łączna liczba rdzeni to {2}. diff --git a/src/Build/Resources/xlf/Strings.pt-BR.xlf b/src/Build/Resources/xlf/Strings.pt-BR.xlf index dba114aa2f6..e999ddb22b0 100644 --- a/src/Build/Resources/xlf/Strings.pt-BR.xlf +++ b/src/Build/Resources/xlf/Strings.pt-BR.xlf @@ -1002,6 +1002,11 @@ Erros: {3} O assembly da tarefa foi carregado de "{0}" enquanto o local desejado era "{1}". + + Cannot modify environment variable '{0}': variable is immutable and must remain constant during the build. + Cannot modify environment variable '{0}': variable is immutable and must remain constant during the build. + + Task "{0}" released {1} cores and now holds {2} cores total. A tarefa "{0}" liberou {1} núcleos e agora contém {2} núcleos no total. diff --git a/src/Build/Resources/xlf/Strings.ru.xlf b/src/Build/Resources/xlf/Strings.ru.xlf index 6b069c84db6..bce9431e594 100644 --- a/src/Build/Resources/xlf/Strings.ru.xlf +++ b/src/Build/Resources/xlf/Strings.ru.xlf @@ -1002,6 +1002,11 @@ Errors: {3} Сборка задачи была загружена из "{0}", а нужное расположение — "{1}". + + Cannot modify environment variable '{0}': variable is immutable and must remain constant during the build. + Cannot modify environment variable '{0}': variable is immutable and must remain constant during the build. + + Task "{0}" released {1} cores and now holds {2} cores total. Задача "{0}" освободила указанное число ядер ({1}). Теперь общее число ядер, которыми располагает задача, равно {2}. diff --git a/src/Build/Resources/xlf/Strings.tr.xlf b/src/Build/Resources/xlf/Strings.tr.xlf index 5f47925406e..1855df13cc3 100644 --- a/src/Build/Resources/xlf/Strings.tr.xlf +++ b/src/Build/Resources/xlf/Strings.tr.xlf @@ -1002,6 +1002,11 @@ Hatalar: {3} İstenilen konum '{1}' iken görev derlemesi '{0}'dan yüklendi. + + Cannot modify environment variable '{0}': variable is immutable and must remain constant during the build. + Cannot modify environment variable '{0}': variable is immutable and must remain constant during the build. + + Task "{0}" released {1} cores and now holds {2} cores total. "{0}" görevi {1} çekirdeği serbest bıraktı. Şu anda toplam {2} çekirdek tutuyor. diff --git a/src/Build/Resources/xlf/Strings.zh-Hans.xlf b/src/Build/Resources/xlf/Strings.zh-Hans.xlf index 1e431883513..eba97842f0d 100644 --- a/src/Build/Resources/xlf/Strings.zh-Hans.xlf +++ b/src/Build/Resources/xlf/Strings.zh-Hans.xlf @@ -1002,6 +1002,11 @@ Errors: {3} 已从“{0}”加载任务程序集,但所需位置为“{1}”。 + + Cannot modify environment variable '{0}': variable is immutable and must remain constant during the build. + Cannot modify environment variable '{0}': variable is immutable and must remain constant during the build. + + Task "{0}" released {1} cores and now holds {2} cores total. 任务“{0}”发布了 {1} 个核心,现总共包含 {2} 个核心。 diff --git a/src/Build/Resources/xlf/Strings.zh-Hant.xlf b/src/Build/Resources/xlf/Strings.zh-Hant.xlf index c056dd7c8a1..54feac70a03 100644 --- a/src/Build/Resources/xlf/Strings.zh-Hant.xlf +++ b/src/Build/Resources/xlf/Strings.zh-Hant.xlf @@ -1002,6 +1002,11 @@ Errors: {3} 工作組件已從 '{0}' 載入,但 '{1}' 才是所需的位置。 + + Cannot modify environment variable '{0}': variable is immutable and must remain constant during the build. + Cannot modify environment variable '{0}': variable is immutable and must remain constant during the build. + + Task "{0}" released {1} cores and now holds {2} cores total. 工作 "{0}" 已發行 {1} 個核心,現在共保留 {2} 個核心。 diff --git a/src/Framework.UnitTests/EnvironmentVariableClassifier_Tests.cs b/src/Framework.UnitTests/EnvironmentVariableClassifier_Tests.cs new file mode 100644 index 00000000000..94de68e8e24 --- /dev/null +++ b/src/Framework.UnitTests/EnvironmentVariableClassifier_Tests.cs @@ -0,0 +1,95 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Microsoft.Build.Shared; +using Shouldly; +using Xunit; + +#nullable disable + +namespace Microsoft.Build.Framework.UnitTests +{ + public class EnvironmentVariableClassifier_Tests + { + [Fact] + public void IsImmutable_CustomImmutableVariables_ExactMatchWorks() + { + var classifier = new EnvironmentVariableClassifier([ + "test_env_var_1", + "test_env_var_2" + ], null); + + classifier.IsImmutable("test_env_var_1").ShouldBeTrue(); + classifier.IsImmutable("test_env_var_2").ShouldBeTrue(); + classifier.IsImmutable("test_env_var_3").ShouldBeFalse(); + classifier.IsImmutable("non_immutable_var_1").ShouldBeFalse(); + } + + [Fact] + public void IsImmutable_CustomPrefixes_WorksCorrectly() + { + var classifier = new EnvironmentVariableClassifier([], ["prefix_1", "prefix_2"]); + + // Custom prefixes should work + classifier.IsImmutable("prefix_1").ShouldBeTrue(); + classifier.IsImmutable("prefix_1_var").ShouldBeTrue(); + classifier.IsImmutable("prefix_2_var").ShouldBeTrue(); + + // Non-matching prefixes should not work + classifier.IsImmutable("test_prefix_1_var").ShouldBeFalse(); + classifier.IsImmutable("non_immutable_var_1").ShouldBeFalse(); + } + + [Fact] + public void IsImmutable_EmptyOrNullNames_ReturnsFalse() + { + var classifier = new EnvironmentVariableClassifier([ + "test_env_var_1" + ], null); + + classifier.IsImmutable(null).ShouldBeFalse(); + classifier.IsImmutable("").ShouldBeFalse(); + classifier.IsImmutable(string.Empty).ShouldBeFalse(); + } + + [WindowsOnlyFact] + public void IsImmutable_CaseSensitivityBehavior_Windows() + { + var classifier = new EnvironmentVariableClassifier([ + "test_env_var_1", + ], ["prefix_1"]); + + // On Windows, environment variables should be case-insensitive + classifier.IsImmutable("test_env_var_1").ShouldBeTrue(); + classifier.IsImmutable("TEST_ENV_VAR_1").ShouldBeTrue(); + classifier.IsImmutable("Test_Env_Var_1").ShouldBeTrue(); + + // Custom prefixes should also be case-insensitive + classifier.IsImmutable("prefix_1").ShouldBeTrue(); + classifier.IsImmutable("PREFIX_1").ShouldBeTrue(); + classifier.IsImmutable("prefix_1_var").ShouldBeTrue(); + classifier.IsImmutable("PREFIX_1_VAR").ShouldBeTrue(); + classifier.IsImmutable("Prefix_1_Var").ShouldBeTrue(); + } + + [UnixOnlyFact] + public void IsImmutable_CaseSensitivityBehavior_Unix() + { + var classifier = new EnvironmentVariableClassifier([ + "test_env_var_1", + ], ["prefix_1"]); + + // On Unix, environment variables should be case-sensitive + classifier.IsImmutable("test_env_var_1").ShouldBeTrue(); + classifier.IsImmutable("TEST_ENV_VAR_1").ShouldBeFalse(); + classifier.IsImmutable("Test_Env_Var_1").ShouldBeFalse(); + + // Custom prefixes should also be case-sensitive + classifier.IsImmutable("prefix_1").ShouldBeTrue(); + classifier.IsImmutable("prefix_1_var").ShouldBeTrue(); + classifier.IsImmutable("PREFIX_1").ShouldBeFalse(); + classifier.IsImmutable("PREFIX_1_VAR").ShouldBeFalse(); + classifier.IsImmutable("Prefix_1_Var").ShouldBeFalse(); + } + } +} diff --git a/src/Framework/Constants.cs b/src/Framework/Constants.cs index 36dbb4aab9f..ade3ad3af56 100644 --- a/src/Framework/Constants.cs +++ b/src/Framework/Constants.cs @@ -10,11 +10,6 @@ namespace Microsoft.Build.Framework /// internal static class Constants { - /// - /// Defines the name of dotnet host path environment variable (e.g DOTNET_HOST_PATH = C:\msbuild\.dotnet\dotnet.exe). - /// - internal const string DotnetHostPathEnvVarName = "DOTNET_HOST_PATH"; - /// /// The project property name used to get the path to the MSBuild assembly. /// @@ -94,4 +89,51 @@ internal static class Constants internal const string TaskHostExplicitlyRequested = "TaskHostExplicitlyRequested"; } + + /// + /// Environment variable names used by MSBuild for immutable variable classification. + /// These constants are excluded from TASKHOST context to avoid validation overhead. + /// + internal static class EnvironmentVariablesNames + { + /// + /// Name of the environment variable that points to 64-bit program files directory. + /// + internal const string ProgramW6432 = "ProgramW6432"; + + /// + /// Name of the environment variable that points to program files directory. + /// + internal const string ProgramFiles = "ProgramFiles"; + + /// + /// Name of the environment variable for .NET Framework installation root. + /// + internal const string ComplusInstallRoot = "COMPLUS_INSTALLROOT"; + + /// + /// Name of the environment variable for .NET Framework version override. + /// + internal const string ComplusVersion = "COMPLUS_VERSION"; + + /// + /// Name of the environment variable for reference assembly root path. + /// + internal const string ReferenceAssemblyRoot = "ReferenceAssemblyRoot"; + + /// + /// Name of the environment variable for .NET Framework installation root (alternate casing). + /// + internal const string ComplusInstallRootAlt = "COMPLUS_InstallRoot"; + + /// + /// Name of the environment variable for .NET Framework version (alternate casing). + /// + internal const string ComplusVersionAlt = "COMPLUS_Version"; + + /// + /// Defines the name of dotnet host path environment variable. + /// + internal const string DotnetHostPath = "DOTNET_HOST_PATH"; + } } diff --git a/src/Framework/EnvironmentVariableClassifier.cs b/src/Framework/EnvironmentVariableClassifier.cs new file mode 100644 index 00000000000..79467452dd7 --- /dev/null +++ b/src/Framework/EnvironmentVariableClassifier.cs @@ -0,0 +1,104 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections.Frozen; +using System.Collections.Generic; + +namespace Microsoft.Build.Framework +{ + /// + /// Classifies environment variables as immutable or mutable. + /// + /// + /// Used in multithreaded build scenarios to prevent tasks from modifying environment variables that could affect other concurrently building projects. + /// + internal sealed class EnvironmentVariableClassifier + { + /// + /// Shared instance used by MSBuild for environment variable classification. + /// + /// + /// Deferred creation avoids overhead in multi-process builds where environment virtualization is not used. + /// + private static readonly Lazy s_instance = new(() => new EnvironmentVariableClassifier()); + + /// + /// Gets the shared singleton instance used for classifying environment variables during builds. + /// + internal static EnvironmentVariableClassifier Instance => s_instance.Value; + + /// + /// Set of specific environment variable names that are classified as immutable. + /// + private readonly FrozenSet _immutableVariables; + + /// + /// Prefixes that identify immutable environment variables. + /// Any variable starting with one of these prefixes is considered immutable. + /// + private readonly IReadOnlyList _immutablePrefixes; + + /// + /// Initializes a new instance with the default set of immutable environment variables and prefixes. + /// + private EnvironmentVariableClassifier() + { + _immutableVariables = FrozenSet.ToFrozenSet([ + // Environment variables used by FrameworkLocationHelper and ToolLocationHelper for framework/SDK discovery. + EnvironmentVariablesNames.ComplusInstallRoot, + EnvironmentVariablesNames.ComplusVersion, + EnvironmentVariablesNames.ReferenceAssemblyRoot, + EnvironmentVariablesNames.ProgramW6432 + ], FrameworkFileUtilities.EnvironmentVariableComparer); + + // On case-sensitive systems, both "MSBUILD" and "MSBuild" prefixes are used + var prefixSet = new HashSet(FrameworkFileUtilities.EnvironmentVariableComparer) { "MSBUILD", "MSBuild" }; + _immutablePrefixes = new List(prefixSet); + } + + /// + /// Initializes a new instance with a custom set of immutable environment variables and prefixes. + /// + /// Set of environment variable names to treat as immutable. + /// Prefixes that identify immutable environment variables. If null or empty, no prefix matching is performed. + /// + /// This constructor is primarily intended for testing scenarios where custom immutability rules are needed. + /// + internal EnvironmentVariableClassifier(IEnumerable immutableVariables, string[] immutablePrefixes) + { + _immutableVariables = FrozenSet.ToFrozenSet(immutableVariables, FrameworkFileUtilities.EnvironmentVariableComparer); + _immutablePrefixes = immutablePrefixes ?? []; + } + + /// + /// Determines whether the specified environment variable is classified as immutable. + /// + /// The environment variable name to check. + /// True if the variable is immutable, false otherwise. + internal bool IsImmutable(string name) + { + if (string.IsNullOrEmpty(name)) + { + return false; + } + + // Check specific variables that are configured as constant + if (_immutableVariables.Contains(name)) + { + return true; + } + + // Check if variable starts with any of the configured immutable prefixes + foreach (string prefix in _immutablePrefixes) + { + if (name.StartsWith(prefix, FrameworkFileUtilities.EnvironmentVariableComparison)) + { + return true; + } + } + + return false; + } + } +} diff --git a/src/Framework/FileUtilities.cs b/src/Framework/FileUtilities.cs index d861b6821ee..7114502cae0 100644 --- a/src/Framework/FileUtilities.cs +++ b/src/Framework/FileUtilities.cs @@ -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. +using System; + #if !TASKHOST using System.Threading; #endif @@ -27,6 +29,18 @@ internal static class FrameworkFileUtilities internal static readonly char[] Slashes = [UnixDirectorySeparator, WindowsDirectorySeparator]; + /// + /// The string comparison to use for environment variable name comparisons, based on OS environment variable handling. + /// Windows: case-insensitive, Unix/Linux: case-sensitive. + /// + internal static readonly StringComparison EnvironmentVariableComparison = NativeMethods.IsWindows ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal; + + /// + /// The string comparer to use for environment variable name comparisons, based on OS environment variable handling. + /// Windows: case-insensitive, Unix/Linux: case-sensitive. + /// + internal static readonly StringComparer EnvironmentVariableComparer = NativeMethods.IsWindows ? StringComparer.OrdinalIgnoreCase : StringComparer.Ordinal; + #if !TASKHOST /// /// AsyncLocal working directory for use during property/item expansion in multithreaded mode. diff --git a/src/Framework/Traits.cs b/src/Framework/Traits.cs index f14208e145a..999fd5a833b 100644 --- a/src/Framework/Traits.cs +++ b/src/Framework/Traits.cs @@ -258,6 +258,13 @@ internal class EscapeHatches /// public readonly bool AlwaysDoImmutableFilesUpToDateCheck = Environment.GetEnvironmentVariable("MSBUILDDONOTCACHEMODIFICATIONTIME") == "1"; + /// + /// Disables the check that prevents tasks from modifying immutable environment variables (e.g., MSBUILD* variables) + /// in multithreaded build mode. This escape hatch should only be used for compatibility with tasks that need to + /// modify these variables and understand the risks of doing so in a concurrent build environment. + /// + public readonly bool DisableImmutableEnvironmentVariableCheck = Environment.GetEnvironmentVariable("MSBUILDDISABLEIMMUTABLEENVIRONMENTVARIABLECHECK") == "1"; + /// /// When copying over an existing file, copy directly into the existing file rather than deleting and recreating. /// diff --git a/src/MSBuild.UnitTests/MSBuildMultithreaded_Tests.cs b/src/MSBuild.UnitTests/MSBuildMultithreaded_Tests.cs index 3b932d38924..d3785ad03c3 100644 --- a/src/MSBuild.UnitTests/MSBuildMultithreaded_Tests.cs +++ b/src/MSBuild.UnitTests/MSBuildMultithreaded_Tests.cs @@ -62,7 +62,7 @@ private bool VerifyTaskEnvironment() private bool TestEnvironmentIsolation() { string mode = IsMultithreadedMode ? "MultiThreaded" : "MultiProcess"; - string envVarName = $"MSBUILD_MULTITHREADED_TEST_VAR_{Guid.NewGuid():N}"; + string envVarName = $"TEST_ENV_VAR_MULTITHREADED_{Guid.NewGuid():N}"; string envVarValue = "TestValue"; // Set environment variable using TaskEnvironment diff --git a/src/Shared/FrameworkLocationHelper.cs b/src/Shared/FrameworkLocationHelper.cs index a7e5d74b727..d90bc73e5c5 100644 --- a/src/Shared/FrameworkLocationHelper.cs +++ b/src/Shared/FrameworkLocationHelper.cs @@ -766,8 +766,8 @@ internal static string GetPathToDotNetFramework(Version version, DotNetFramework private static bool CheckForFrameworkInstallation(string registryEntryToCheckInstall, string registryValueToCheckInstall) { // Get the complus install root and version - string complusInstallRoot = Environment.GetEnvironmentVariable("COMPLUS_INSTALLROOT"); - string complusVersion = Environment.GetEnvironmentVariable("COMPLUS_VERSION"); + string complusInstallRoot = Environment.GetEnvironmentVariable(EnvironmentVariablesNames.ComplusInstallRoot); + string complusVersion = Environment.GetEnvironmentVariable(EnvironmentVariablesNames.ComplusVersion); // Complus is not set we need to make sure the framework we are targeting is installed. Check the registry key before trying to find the directory. // If complus is set then we will return that directory as the framework directory, there is no need to check the registry value for the framework and it may not even be installed. @@ -818,8 +818,8 @@ internal static string FindDotNetFrameworkPath( } // If the COMPLUS variables are set, they override everything -- that's the directory we want. - string complusInstallRoot = Environment.GetEnvironmentVariable("COMPLUS_INSTALLROOT"); - string complusVersion = Environment.GetEnvironmentVariable("COMPLUS_VERSION"); + string complusInstallRoot = Environment.GetEnvironmentVariable(EnvironmentVariablesNames.ComplusInstallRoot); + string complusVersion = Environment.GetEnvironmentVariable(EnvironmentVariablesNames.ComplusVersion); if (!String.IsNullOrEmpty(complusInstallRoot) && !String.IsNullOrEmpty(complusVersion)) { @@ -935,7 +935,7 @@ internal static string GenerateProgramFiles64() // either we're in a 32-bit window, or we're on a 32-bit machine. // if we're on a 32-bit machine, ProgramW6432 won't exist // if we're on a 64-bit machine, ProgramW6432 will point to the correct Program Files. - programFilesX64 = Environment.GetEnvironmentVariable("ProgramW6432"); + programFilesX64 = Environment.GetEnvironmentVariable(EnvironmentVariablesNames.ProgramW6432); } else { @@ -953,7 +953,7 @@ internal static string GenerateProgramFiles64() /// internal static string GenerateProgramFilesReferenceAssemblyRoot() { - string combinedPath = Environment.GetEnvironmentVariable("ReferenceAssemblyRoot"); + string combinedPath = Environment.GetEnvironmentVariable(EnvironmentVariablesNames.ReferenceAssemblyRoot); if (!String.IsNullOrEmpty(combinedPath)) { combinedPath = Path.GetFullPath(combinedPath); diff --git a/src/Tasks.UnitTests/RoslynCodeTaskFactory_Tests.cs b/src/Tasks.UnitTests/RoslynCodeTaskFactory_Tests.cs index 0253b720504..887834995c5 100644 --- a/src/Tasks.UnitTests/RoslynCodeTaskFactory_Tests.cs +++ b/src/Tasks.UnitTests/RoslynCodeTaskFactory_Tests.cs @@ -899,7 +899,7 @@ public void RoslynCodeTaskFactory_UsingAPI(bool forceOutOfProc) } RunnerUtilities.ApplyDotnetHostPathEnvironmentVariable(env); - var dotnetPath = Environment.GetEnvironmentVariable("DOTNET_HOST_PATH"); + var dotnetPath = Environment.GetEnvironmentVariable(EnvironmentVariablesNames.DotnetHostPath); var project = env.CreateTestProjectWithFiles("p1.proj", text); var logger = project.BuildProjectExpectSuccess(); diff --git a/src/Tasks/Al.cs b/src/Tasks/Al.cs index d1872e4fb39..6dff2b04225 100644 --- a/src/Tasks/Al.cs +++ b/src/Tasks/Al.cs @@ -305,7 +305,7 @@ protected override string GenerateFullPathToTool() // If COMPLUS_InstallRoot\COMPLUS_Version are set (the dogfood world), we want to find it there, instead of // the SDK, which may or may not be installed. The following will look there. - if (!String.IsNullOrEmpty(Environment.GetEnvironmentVariable("COMPLUS_InstallRoot")) || !String.IsNullOrEmpty(Environment.GetEnvironmentVariable("COMPLUS_Version"))) + if (!String.IsNullOrEmpty(Environment.GetEnvironmentVariable(EnvironmentVariablesNames.ComplusInstallRootAlt)) || !String.IsNullOrEmpty(Environment.GetEnvironmentVariable(EnvironmentVariablesNames.ComplusVersionAlt))) { pathToTool = ToolLocationHelper.GetPathToDotNetFrameworkFile(ToolExe, TargetDotNetFrameworkVersion.Latest); } diff --git a/src/Tasks/RoslynCodeTaskFactory/RoslynCodeTaskFactoryCompilers.cs b/src/Tasks/RoslynCodeTaskFactory/RoslynCodeTaskFactoryCompilers.cs index 64f18fab383..750afbb5902 100644 --- a/src/Tasks/RoslynCodeTaskFactory/RoslynCodeTaskFactoryCompilers.cs +++ b/src/Tasks/RoslynCodeTaskFactory/RoslynCodeTaskFactoryCompilers.cs @@ -10,7 +10,6 @@ #if RUNTIME_TYPE_NETCORE using System.Runtime.InteropServices; using Microsoft.Build.Shared; -using Constants = Microsoft.Build.Framework.Constants; #endif #nullable disable @@ -53,7 +52,7 @@ protected RoslynCodeTaskFactoryCompilerBase() #if RUNTIME_TYPE_NETCORE // Tools and MSBuild Tasks within the SDK that invoke binaries via the dotnet host are expected // to honor the environment variable DOTNET_HOST_PATH to ensure a consistent experience. - _dotnetCliPath = Environment.GetEnvironmentVariable(Constants.DotnetHostPathEnvVarName); + _dotnetCliPath = Environment.GetEnvironmentVariable(EnvironmentVariablesNames.DotnetHostPath); if (string.IsNullOrEmpty(_dotnetCliPath)) { // Fallback to get dotnet path from current process which might be dotnet executable. diff --git a/src/Tasks/SGen.cs b/src/Tasks/SGen.cs index 7be17cf5dc2..3a36ae20f9a 100644 --- a/src/Tasks/SGen.cs +++ b/src/Tasks/SGen.cs @@ -307,7 +307,7 @@ protected override string GenerateFullPathToTool() // If COMPLUS_InstallRoot\COMPLUS_Version are set (the dogfood world), we want to find it there, instead of // the SDK, which may or may not be installed. The following will look there. - if (!String.IsNullOrEmpty(Environment.GetEnvironmentVariable("COMPLUS_InstallRoot")) || !String.IsNullOrEmpty(Environment.GetEnvironmentVariable("COMPLUS_Version"))) + if (!String.IsNullOrEmpty(Environment.GetEnvironmentVariable(EnvironmentVariablesNames.ComplusInstallRootAlt)) || !String.IsNullOrEmpty(Environment.GetEnvironmentVariable(EnvironmentVariablesNames.ComplusVersionAlt))) { pathToTool = ToolLocationHelper.GetPathToDotNetFrameworkFile(ToolExe, TargetDotNetFrameworkVersion.Latest); } diff --git a/src/UnitTests.Shared/RunnerUtilities.cs b/src/UnitTests.Shared/RunnerUtilities.cs index e6ac57de4c5..ca1e596475e 100644 --- a/src/UnitTests.Shared/RunnerUtilities.cs +++ b/src/UnitTests.Shared/RunnerUtilities.cs @@ -39,7 +39,7 @@ public static class RunnerUtilities public static void ApplyDotnetHostPathEnvironmentVariable(TestEnvironment testEnvironment) { // Built msbuild.dll executed by dotnet.exe needs this environment variable for msbuild tasks such as RoslynCodeTaskFactory. - testEnvironment.SetEnvironmentVariable(Constants.DotnetHostPathEnvVarName, s_dotnetExePath); + testEnvironment.SetEnvironmentVariable(EnvironmentVariablesNames.DotnetHostPath, s_dotnetExePath); } #endif