| File: Traits.cs | Web Access |
| Project: src\msbuild\src\Framework\Microsoft.Build.Framework.csproj (Microsoft.Build.Framework) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System; namespace Microsoft.Build.Framework { /// <summary> /// Represents toggleable features of the MSBuild engine. /// </summary> internal class Traits { private static Traits _instance = new Traits(); public static Traits Instance { get { if (BuildEnvironmentState.s_runningTests) { return new Traits(); } return _instance; } } public Traits() { EscapeHatches = new EscapeHatches(); DebugScheduler = !string.IsNullOrEmpty(Environment.GetEnvironmentVariable("MSBUILDDEBUGSCHEDULER")); DebugNodeCommunication = DebugEngine || !string.IsNullOrEmpty(Environment.GetEnvironmentVariable("MSBUILDDEBUGCOMM")); } public EscapeHatches EscapeHatches { get; } internal readonly string? MSBuildDisableFeaturesFromVersion = Environment.GetEnvironmentVariable("MSBUILDDISABLEFEATURESFROMVERSION"); // This will affect all tasks except for MSBuild and CallTarget. Those two have to run in-proc, as they depend on IBuildEngine callbacks. public readonly bool ForceAllTasksOutOfProcToTaskHost = Environment.GetEnvironmentVariable("MSBUILDFORCEALLTASKSOUTOFPROC") == "1"; /// <summary> /// Force MSBuild to run in multi-threaded mode (using in-proc nodes for parallel build), /// equivalent to passing -multiThreaded / -mt on the command line. /// </summary> public readonly bool ForceMultiThreaded = Environment.GetEnvironmentVariable("MSBUILDFORCEMULTITHREADED") == "1"; /// <summary> /// Do not expand wildcards that match a certain pattern /// </summary> public readonly bool UseLazyWildCardEvaluation = !string.IsNullOrEmpty(Environment.GetEnvironmentVariable("MsBuildSkipEagerWildCardEvaluationRegexes")); public readonly bool LogExpandedWildcards = !string.IsNullOrEmpty(Environment.GetEnvironmentVariable("MSBUILDLOGEXPANDEDWILDCARDS")); public readonly bool ThrowOnDriveEnumeratingWildcard = Environment.GetEnvironmentVariable("MSBUILDFAILONDRIVEENUMERATINGWILDCARD") == "1"; /// <summary> /// Cache file existence for the entire process /// </summary> public readonly bool CacheFileExistence = !string.IsNullOrEmpty(Environment.GetEnvironmentVariable("MsBuildCacheFileExistence")); public readonly bool UseSimpleProjectRootElementCacheConcurrency = !string.IsNullOrEmpty(Environment.GetEnvironmentVariable("MsBuildUseSimpleProjectRootElementCacheConcurrency")); /// <summary> /// Cache wildcard expansions for the entire process /// </summary> public readonly bool MSBuildCacheFileEnumerations = !string.IsNullOrEmpty(Environment.GetEnvironmentVariable("MsBuildCacheFileEnumerations")); public readonly bool EnableAllPropertyFunctions = Environment.GetEnvironmentVariable("MSBUILDENABLEALLPROPERTYFUNCTIONS") == "1"; /// <summary> /// Enable restore first functionality in MSBuild.exe /// </summary> public readonly bool EnableRestoreFirst = Environment.GetEnvironmentVariable("MSBUILDENABLERESTOREFIRST") == "1"; /// <summary> /// Allow the user to specify that two processes should not be communicating via an environment variable. /// </summary> public static readonly string? MSBuildNodeHandshakeSalt = Environment.GetEnvironmentVariable("MSBUILDNODEHANDSHAKESALT"); /// <summary> /// Override property "MSBuildRuntimeType" to "Full", ignoring the actual runtime type of MSBuild. /// </summary> public readonly bool ForceEvaluateAsFullFramework = !string.IsNullOrEmpty(Environment.GetEnvironmentVariable("MsBuildForceEvaluateAsFullFramework")); /// <summary> /// Setting the associated environment variable to 1 restores the pre-15.8 single /// threaded (slower) copy behavior. Zero implies Int32.MaxValue, less than zero /// (default) uses the empirical default in Copy.cs, greater than zero can allow /// perf tuning beyond the defaults chosen. /// </summary> public readonly int CopyTaskParallelism = EnvironmentUtilities.GetValueAsInt32OrDefault("MSBUILDCOPYTASKPARALLELISM", -1); /// <summary> /// Instruct MSBuild to write out the generated "metaproj" file to disk when building a solution file. /// </summary> public readonly bool EmitSolutionMetaproj = !string.IsNullOrEmpty(Environment.GetEnvironmentVariable("MSBuildEmitSolution")); /// <summary> /// Modifies Solution Generator to generate a metaproj that batches multiple Targets into one MSBuild task invoke. /// </summary> /// <remarks> /// For example, a run of Clean;Build target will first run Clean on all projects, /// then run Build on all projects. When enabled, it will run Clean;Build on all /// Projects at the back to back. Allowing the second target to start sooner than before. /// </remarks> public readonly bool SolutionBatchTargets = !string.IsNullOrEmpty(Environment.GetEnvironmentVariable("MSBuildSolutionBatchTargets")); /// <summary> /// Log statistics about property functions which require reflection /// </summary> public readonly bool LogPropertyFunctionsRequiringReflection = !string.IsNullOrEmpty(Environment.GetEnvironmentVariable("MSBuildLogPropertyFunctionsRequiringReflection")); /// <summary> /// Log all assembly loads including those that come from known MSBuild and .NET SDK sources in the binary log. /// </summary> public readonly bool LogAllAssemblyLoads = !string.IsNullOrEmpty(Environment.GetEnvironmentVariable("MSBUILDLOGALLASSEMBLYLOADS")); /// <summary> /// Log all environment variables whether or not they are used in a build in the binary log. /// </summary> public static bool LogAllEnvironmentVariables = !string.IsNullOrEmpty(Environment.GetEnvironmentVariable("MSBUILDLOGALLENVIRONMENTVARIABLES")); /// <summary> /// Log property tracking information. /// </summary> public readonly int LogPropertyTracking = EnvironmentUtilities.GetValueAsInt32OrDefault("MsBuildLogPropertyTracking", 0); // Default to logging nothing via the property tracker. /// <summary> /// When evaluating items, this is the minimum number of items on the running list to use a dictionary-based remove optimization. /// </summary> public readonly int DictionaryBasedItemRemoveThreshold = EnvironmentUtilities.GetValueAsInt32OrDefault("MSBUILDDICTIONARYBASEDITEMREMOVETHRESHOLD", 100); /// <summary> /// Size in bytes of the kernel buffers backing the named pipes used to communicate with out-of-process /// .NET nodes (worker nodes and .NET TaskHosts). A larger buffer lets the sending side queue more (or /// larger) packets before it blocks waiting for the receiver to drain, which removes most of the /// backpressure stalls when shipping large TaskHostConfiguration packets to sidecar TaskHosts in /// multi-threaded (-mt) builds. Tunable via MSBUILDNODECONNECTIONBUFFERSIZE; when unset it defaults to /// 1 MB under change wave 18.9, falling back to the historical 128 KB when that wave is opted out. /// Note: the legacy .NET Framework 3.5 task host (MSBuildTaskHost) uses its own endpoint and is /// intentionally unaffected by this setting - it keeps the historical 128 KB buffer. /// </summary> public readonly int NodeConnectionBufferSize = GetNodeConnectionBufferSize(); private static int GetNodeConnectionBufferSize() { int configured = EnvironmentUtilities.GetValueAsInt32OrDefault("MSBUILDNODECONNECTIONBUFFERSIZE", -1); if (configured > 0) { return configured; } const int DefaultBufferSize = 1024 * 1024; const int LegacyBufferSize = 128 * 1024; return ChangeWaves.AreFeaturesEnabled(ChangeWaves.Wave18_9) ? DefaultBufferSize : LegacyBufferSize; } /// <summary> /// Launches a persistent RAR process. /// </summary> /// TODO: Replace with command line flag when feature is completed. The environment variable is intented to avoid exposing the flag early. public readonly bool EnableRarNode = !string.IsNullOrEmpty(Environment.GetEnvironmentVariable("MSBuildRarNode")); /// <summary> /// Enables the build coordinator for cross-process node budget management. /// </summary> public readonly bool EnableCoordinator = !string.IsNullOrEmpty(Environment.GetEnvironmentVariable(UseCoordinatorEnvVarName)); /// <summary> /// Name of environment variable used to enable the build coordinator. /// </summary> public const string UseCoordinatorEnvVarName = "MSBUILDUSECOORDINATOR"; /// <summary> /// Name of environment variables used to enable MSBuild server. /// </summary> public const string UseMSBuildServerEnvVarName = "MSBUILDUSESERVER"; /// <summary> /// Name of environment variable for logging arguments (e.g., -bl, -check). /// </summary> public const string MSBuildLoggingArgsEnvVarName = "MSBUILD_LOGGING_ARGS"; /// <summary> /// Name of environment variable that controls the logging level for diagnostic messages /// emitted when processing the MSBUILD_LOGGING_ARGS environment variable. /// Set to "message" to emit as low-importance build messages instead of console warnings. /// </summary> public const string MSBuildLoggingArgsLevelEnvVarName = "MSBUILD_LOGGING_ARGS_LEVEL"; /// <summary> /// Value of the MSBUILD_LOGGING_ARGS environment variable. /// </summary> public static string? MSBuildLoggingArgs => Environment.GetEnvironmentVariable(MSBuildLoggingArgsEnvVarName); /// <summary> /// Gets if the logging level for MSBUILD_LOGGING_ARGS diagnostic is message. /// </summary> public readonly bool EmitLogsAsMessage = string.Equals(Environment.GetEnvironmentVariable(MSBuildLoggingArgsLevelEnvVarName), "message", StringComparison.OrdinalIgnoreCase); public readonly bool DebugEngine = !string.IsNullOrEmpty(Environment.GetEnvironmentVariable("MSBuildDebugEngine")); public readonly bool DebugScheduler; public readonly bool DebugNodeCommunication; public readonly bool DebugUnitTests = !string.IsNullOrEmpty(Environment.GetEnvironmentVariable("MSBuildDebugUnitTests")); public readonly bool InProcNodeDisabled = Environment.GetEnvironmentVariable("MSBUILDNOINPROCNODE") == "1"; /// <summary> /// Forces execution of tasks coming from a different TaskFactory than AssemblyTaskFactory out of proc. /// </summary> public readonly bool ForceTaskFactoryOutOfProc = Environment.GetEnvironmentVariable("MSBUILDFORCEINLINETASKFACTORIESOUTOFPROC") == "1"; /// <summary> /// Make Console use default encoding in the system. It opts out automatic console encoding UTF-8. /// </summary> public readonly bool ConsoleUseDefaultEncoding = Environment.GetEnvironmentVariable("MSBUILD_CONSOLE_USE_DEFAULT_ENCODING") == "1" || Environment.GetEnvironmentVariable("DOTNET_CLI_CONSOLE_USE_DEFAULT_ENCODING") == "1"; /// <summary> /// Variables controlling opt out at the level of not initializing telemetry infrastructure. Set to "1" or "true" to opt out. /// mirroring /// https://learn.microsoft.com/en-us/dotnet/core/tools/telemetry /// </summary> public bool SdkTelemetryOptOut = EnvironmentUtilities.IsValueOneOrTrue("DOTNET_CLI_TELEMETRY_OPTOUT"); public bool FrameworkTelemetryOptOut = EnvironmentUtilities.IsValueOneOrTrue("MSBUILD_TELEMETRY_OPTOUT"); public bool ExcludeTasksDetailsFromTelemetry = EnvironmentUtilities.IsValueOneOrTrue("MSBUILDTELEMETRYEXCLUDETASKSDETAILS"); public bool FlushNodesTelemetryIntoConsole = EnvironmentUtilities.IsValueOneOrTrue("MSBUILDFLUSHNODESTELEMETRYINTOCONSOLE"); public bool EnableTargetOutputLogging = EnvironmentUtilities.IsValueOneOrTrue("MSBUILDTARGETOUTPUTLOGGING"); // for VS17.14 public readonly bool SlnParsingWithSolutionPersistenceOptIn = !string.IsNullOrEmpty(Environment.GetEnvironmentVariable("MSBUILD_PARSE_SLN_WITH_SOLUTIONPERSISTENCE")); public static void UpdateFromEnvironment() { // Re-create Traits instance to update values in Traits according to current environment. _instance = new Traits(); } } internal class EscapeHatches { /// <summary> /// Do not log command line information to build loggers. Useful to unbreak people who parse the msbuild log and who are unwilling to change their code. /// </summary> public readonly bool DoNotSendDeferredMessagesToBuildManager = !string.IsNullOrEmpty(Environment.GetEnvironmentVariable("MsBuildDoNotSendDeferredMessagesToBuildManager")); /// <summary> /// https://github.com/dotnet/msbuild/pull/4975 started expanding qualified metadata in Update operations. Before they'd expand to empty strings. /// This escape hatch turns back the old empty string behavior. /// </summary> public readonly bool DoNotExpandQualifiedMetadataInUpdateOperation = !string.IsNullOrEmpty(Environment.GetEnvironmentVariable("MSBuildDoNotExpandQualifiedMetadataInUpdateOperation")); /// <summary> /// Force whether Project based evaluations should evaluate elements with false conditions. /// </summary> public readonly bool? EvaluateElementsWithFalseConditionInProjectEvaluation = ParseNullableBoolFromEnvironmentVariable("MSBUILDEVALUATEELEMENTSWITHFALSECONDITIONINPROJECTEVALUATION"); /// <summary> /// Always use the accurate-but-slow CreateFile approach to timestamp extraction. /// </summary> public readonly bool AlwaysUseContentTimestamp = Environment.GetEnvironmentVariable("MSBUILDALWAYSCHECKCONTENTTIMESTAMP") == "1"; /// <summary> /// Truncate task inputs when logging them. This can reduce memory pressure /// at the expense of log usefulness. /// </summary> public readonly bool TruncateTaskInputs = Environment.GetEnvironmentVariable("MSBUILDTRUNCATETASKINPUTS") == "1"; /// <summary> /// Disables truncation of Condition messages in Tasks/Targets via ExpanderOptions.Truncate. /// </summary> public readonly bool DoNotTruncateConditions = Environment.GetEnvironmentVariable("MSBuildDoNotTruncateConditions") == "1"; /// <summary> /// Disables skipping full drive/filesystem globs that are behind a false condition. /// </summary> public readonly bool AlwaysEvaluateDangerousGlobs = Environment.GetEnvironmentVariable("MSBuildAlwaysEvaluateDangerousGlobs") == "1"; /// <summary> /// Disables skipping full up to date check for immutable files. See FileClassifier class. /// </summary> public readonly bool AlwaysDoImmutableFilesUpToDateCheck = Environment.GetEnvironmentVariable("MSBUILDDONOTCACHEMODIFICATIONTIME") == "1"; /// <summary> /// When copying over an existing file, copy directly into the existing file rather than deleting and recreating. /// </summary> public readonly bool CopyWithoutDelete = Environment.GetEnvironmentVariable("MSBUILDCOPYWITHOUTDELETE") == "1"; /// <summary> /// Emit events for project imports. /// </summary> private bool? _logProjectImports; /// <summary> /// Emit events for project imports. /// </summary> public bool LogProjectImports { get { // Cache the first time if (_logProjectImports == null) { _logProjectImports = !String.IsNullOrEmpty(Environment.GetEnvironmentVariable("MSBUILDLOGIMPORTS")); } return _logProjectImports.Value; } set { _logProjectImports = value; } } private bool? _logTaskInputs; public bool LogTaskInputs { get { if (_logTaskInputs == null) { _logTaskInputs = Environment.GetEnvironmentVariable("MSBUILDLOGTASKINPUTS") == "1"; } return _logTaskInputs.Value; } set { _logTaskInputs = value; } } private bool? _logPropertiesAndItemsAfterEvaluation; private bool _logPropertiesAndItemsAfterEvaluationInitialized = false; public bool? LogPropertiesAndItemsAfterEvaluation { get { if (!_logPropertiesAndItemsAfterEvaluationInitialized) { _logPropertiesAndItemsAfterEvaluationInitialized = true; var variable = Environment.GetEnvironmentVariable("MSBUILDLOGPROPERTIESANDITEMSAFTEREVALUATION"); if (!string.IsNullOrEmpty(variable)) { _logPropertiesAndItemsAfterEvaluation = variable == "1" || string.Equals(variable, "true", StringComparison.OrdinalIgnoreCase); } } return _logPropertiesAndItemsAfterEvaluation; } set { _logPropertiesAndItemsAfterEvaluationInitialized = true; _logPropertiesAndItemsAfterEvaluation = value; } } /// <summary> /// Read information only once per file per ResolveAssemblyReference invocation. /// </summary> public readonly bool CacheAssemblyInformation = Environment.GetEnvironmentVariable("MSBUILDDONOTCACHERARASSEMBLYINFORMATION") != "1"; public readonly ProjectInstanceTranslationMode? ProjectInstanceTranslation = ComputeProjectInstanceTranslation(); /// <summary> /// Never use the slow (but more accurate) CreateFile approach to timestamp extraction. /// </summary> public readonly bool UseSymlinkTimeInsteadOfTargetTime = Environment.GetEnvironmentVariable("MSBUILDUSESYMLINKTIMESTAMP") == "1"; /// <summary> /// Allow node reuse of TaskHost nodes. This results in task assemblies locked past the build lifetime, preventing them from being rebuilt if custom tasks change, but may improve performance. /// </summary> public readonly bool ReuseTaskHostNodes = Environment.GetEnvironmentVariable("MSBUILDREUSETASKHOSTNODES") == "1"; /// <summary> /// Whether or not to ignore imports that are considered empty. See ProjectRootElement.IsEmptyXmlFile() for more info. /// </summary> public readonly bool IgnoreEmptyImports = Environment.GetEnvironmentVariable("MSBUILDIGNOREEMPTYIMPORTS") == "1"; /// <summary> /// Whether to respect the TreatAsLocalProperty parameter on the Project tag. /// </summary> public readonly bool IgnoreTreatAsLocalProperty = Environment.GetEnvironmentVariable("MSBUILDIGNORETREATASLOCALPROPERTY") != null; /// <summary> /// Whether to write information about why we evaluate to debug output. /// </summary> public readonly bool DebugEvaluation = Environment.GetEnvironmentVariable("MSBUILDDEBUGEVALUATION") != null; /// <summary> /// Whether to warn when we set a property for the first time, after it was previously used. /// </summary> public readonly bool WarnOnUninitializedProperty = !String.IsNullOrEmpty(Environment.GetEnvironmentVariable("MSBUILDWARNONUNINITIALIZEDPROPERTY")); /// <summary> /// MSBUILDUSECASESENSITIVEITEMNAMES is an escape hatch for the fix /// for https://github.com/dotnet/msbuild/issues/1751. It should /// be removed (permanently set to false) after establishing that /// it's unneeded (at least by the 16.0 timeframe). /// </summary> public readonly bool UseCaseSensitiveItemNames = Environment.GetEnvironmentVariable("MSBUILDUSECASESENSITIVEITEMNAMES") == "1"; /// <summary> /// Disable the use of paths longer than Windows MAX_PATH limits (260 characters) when running on a long path enabled OS. /// </summary> public readonly bool DisableLongPaths = Environment.GetEnvironmentVariable("MSBUILDDISABLELONGPATHS") == "1"; /// <summary> /// Disable the use of any caching when resolving SDKs. /// </summary> public readonly bool DisableSdkResolutionCache = Environment.GetEnvironmentVariable("MSBUILDDISABLESDKCACHE") == "1"; /// <summary> /// Don't delete TargetPath metadata from associated files found by RAR. /// </summary> public readonly bool TargetPathForRelatedFiles = Environment.GetEnvironmentVariable("MSBUILDTARGETPATHFORRELATEDFILES") == "1"; /// <summary> /// Disable AssemblyLoadContext isolation for plugins. /// </summary> public readonly bool UseSingleLoadContext = Environment.GetEnvironmentVariable("MSBUILDSINGLELOADCONTEXT") == "1"; /// <summary> /// Use custom AssemblyLoadContext for loading dependencies found in the MSBuild tools directory. /// When enabled, assemblies found in the MSBuild tools directory are loaded into the plugin's isolated context /// instead of the shared default context, preventing potential version conflicts with the host application. /// </summary> public readonly bool UseCustomLoadContextForDependenciesInToolsDirectory = Environment.GetEnvironmentVariable("MSBUILDUSECUSTOMLOADCONTEXTFORDEPENDENCIESINTOOLSDIRECTORY") == "1"; /// <summary> /// Enables the user of autorun functionality in CMD.exe on Windows which is disabled by default in MSBuild. /// </summary> public readonly bool UseAutoRunWhenLaunchingProcessUnderCmd = Environment.GetEnvironmentVariable("MSBUILDUSERAUTORUNINCMD") == "1"; /// <summary> /// Disables switching codepage to UTF-8 after detection of characters that can't be represented in the current codepage. /// </summary> public readonly bool AvoidUnicodeWhenWritingToolTaskBatch = Environment.GetEnvironmentVariable("MSBUILDAVOIDUNICODE") == "1"; /// <summary> /// Workaround for https://github.com/Microsoft/vstest/issues/1503. /// </summary> public readonly bool EnsureStdOutForChildNodesIsPrimaryStdout = Environment.GetEnvironmentVariable("MSBUILDENSURESTDOUTFORTASKPROCESSES") == "1"; /// <summary> /// Use the original, string-only resx parsing in .NET Core scenarios. /// </summary> /// <remarks> /// Escape hatch for problems arising from https://github.com/dotnet/msbuild/pull/4420. /// </remarks> public readonly bool UseMinimalResxParsingInCoreScenarios = Environment.GetEnvironmentVariable("MSBUILDUSEMINIMALRESX") == "1"; /// <summary> /// Escape hatch to ensure msbuild produces the compatible build results cache without versioning. /// </summary> /// <remarks> /// Escape hatch for problems arising from https://github.com/dotnet/msbuild/issues/10208. /// </remarks> public readonly bool DoNotVersionBuildResult = Environment.GetEnvironmentVariable("MSBUILDDONOTVERSIONBUILDRESULT") == "1"; /// <summary> /// Escape hatch to ensure build check does not limit amount of results. /// </summary> public readonly bool DoNotLimitBuildCheckResultsNumber = Environment.GetEnvironmentVariable("MSBUILDDONOTLIMITBUILDCHECKRESULTSNUMBER") == "1"; private bool _sdkReferencePropertyExpansionInitialized; private SdkReferencePropertyExpansionMode? _sdkReferencePropertyExpansionValue; /// <summary> /// Overrides the default behavior of property expansion on evaluation of a <see cref="Framework.SdkReference"/>. /// </summary> /// <remarks> /// Escape hatch for problems arising from https://github.com/dotnet/msbuild/pull/5552. /// </remarks> public SdkReferencePropertyExpansionMode? SdkReferencePropertyExpansion { get { if (!_sdkReferencePropertyExpansionInitialized) { _sdkReferencePropertyExpansionValue = ComputeSdkReferencePropertyExpansion(); _sdkReferencePropertyExpansionInitialized = true; } return _sdkReferencePropertyExpansionValue; } } private static bool? ParseNullableBoolFromEnvironmentVariable(string environmentVariable) { var value = Environment.GetEnvironmentVariable(environmentVariable); if (string.IsNullOrEmpty(value)) { return null; } return bool.TryParse(value, out bool result) ? result : InternalError.Throw<bool?>($"Environment variable \"{environmentVariable}\" should have values \"true\", \"false\" or undefined"); } private static ProjectInstanceTranslationMode? ComputeProjectInstanceTranslation() { var mode = Environment.GetEnvironmentVariable("MSBUILD_PROJECTINSTANCE_TRANSLATION_MODE"); if (mode == null) { return null; } if (mode.Equals("full", StringComparison.OrdinalIgnoreCase)) { return ProjectInstanceTranslationMode.Full; } if (mode.Equals("partial", StringComparison.OrdinalIgnoreCase)) { return ProjectInstanceTranslationMode.Partial; } return InternalError.Throw<ProjectInstanceTranslationMode?>($"Invalid escape hatch for project instance translation: {mode}"); } private static SdkReferencePropertyExpansionMode? ComputeSdkReferencePropertyExpansion() { var mode = Environment.GetEnvironmentVariable("MSBUILD_SDKREFERENCE_PROPERTY_EXPANSION_MODE"); if (mode == null) { return null; } // The following uses StartsWith instead of Equals to enable possible tricks like // the dpiAware "True/PM" trick (see https://devblogs.microsoft.com/oldnewthing/20160617-00/?p=93695) // in the future. const StringComparison comparison = StringComparison.OrdinalIgnoreCase; if (mode.StartsWith("no", comparison)) { return SdkReferencePropertyExpansionMode.NoExpansion; } if (mode.StartsWith("default", comparison)) { return SdkReferencePropertyExpansionMode.DefaultExpand; } if (mode.StartsWith(nameof(SdkReferencePropertyExpansionMode.ExpandUnescape), comparison)) { return SdkReferencePropertyExpansionMode.ExpandUnescape; } if (mode.StartsWith(nameof(SdkReferencePropertyExpansionMode.ExpandLeaveEscaped), comparison)) { return SdkReferencePropertyExpansionMode.ExpandLeaveEscaped; } return InternalError.Throw<SdkReferencePropertyExpansionMode?>($"Invalid escape hatch for SdkReference property expansion: {mode}"); } public enum ProjectInstanceTranslationMode { Full, Partial } public enum SdkReferencePropertyExpansionMode { NoExpansion, DefaultExpand, ExpandUnescape, ExpandLeaveEscaped } } }