// Copyright (c) Microsoft. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. //----------------------------------------------------------------------- // // Class holding the parameters and settings which are global to the build. //----------------------------------------------------------------------- using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Diagnostics.CodeAnalysis; using System.IO; using System.Threading; using System.Globalization; using Microsoft.Build.BackEnd; using Microsoft.Build.Collections; using Microsoft.Build.Evaluation; using Microsoft.Build.Framework; using Microsoft.Build.Internal; using Microsoft.Build.Shared; using ForwardingLoggerRecord = Microsoft.Build.Logging.ForwardingLoggerRecord; namespace Microsoft.Build.Execution { using Utilities = Microsoft.Build.Internal.Utilities; /// /// This class represents all of the settings which must be specified to start a build. /// public class BuildParameters : INodePacketTranslatable { /// /// The default thread stack size for threads owned by MSBuild. /// private const int DefaultThreadStackSize = 262144; // 256k /// /// The timeout for endpoints to shut down. /// private const int DefaultEndpointShutdownTimeout = 30 * 1000; // 30 seconds /// /// The timeout for the engine to shutdown. /// private const int DefaultEngineShutdownTimeout = Timeout.Infinite; /// /// The shutdown timeout for the logging thread. /// private const int DefaultLoggingThreadShutdownTimeout = 30 * 1000; // 30 seconds /// /// The shutdown timeout for the request builder. /// private const int DefaultRequestBuilderShutdownTimeout = Timeout.Infinite; /// /// The maximum number of idle request builders to retain before we start discarding them. /// private const int DefaultIdleRequestBuilderLimit = 2; /// /// The startup directory. /// private static string s_startupDirectory = NativeMethodsShared.GetCurrentDirectory(); /// /// Indicates whether we should warn when a property is uninitialized when it is used. /// private static bool? s_warnOnUninitializedProperty; /// /// Indicates if we should dump string interning stats. /// private static bool? s_dumpOpportunisticInternStats; /// /// Indicates if we should debug the expander. /// private static bool? s_debugExpansion; /// /// Indicates if we should keep duplicate target outputs. /// private static bool? s_keepDuplicateOutputs; /// /// Indicates if we should enable the build plan /// private static bool? s_enableBuildPlan; /// /// The maximum number of idle request builders we will retain. /// private static int? s_idleRequestBuilderLimit; /// /// Location that msbuild.exe was last successfully found at. /// private static string s_msbuildExeKnownToExistAt; /// /// The build id /// private int _buildId = 0; /// /// The culture /// private CultureInfo _culture = CultureInfo.CurrentCulture; /// /// The default tools version. /// private string _defaultToolsVersion = "2.0"; /// /// When true, causes the build to emit a summary of project build information /// private bool _detailedSummary; /// /// Flag indicating whether node reuse should be enabled. /// By default, it is enabled. /// #if FEATURE_NODE_REUSE private bool _enableNodeReuse = true; #else private bool _enableNodeReuse = false; #endif /// /// The original process environment. /// private Dictionary _buildProcessEnvironment; /// /// The environment properties for the build. /// private PropertyDictionary _environmentProperties = new PropertyDictionary(); /// /// The forwarding logger records. /// private IEnumerable _forwardingLoggers; /// /// The build-global properties. /// private PropertyDictionary _globalProperties = new PropertyDictionary(); /// /// The host services object. /// private HostServices _hostServices; /// /// The loggers. /// private IEnumerable _loggers; /// /// The maximum number of nodes to use. /// private int _maxNodeCount = 1; /// /// The maximum amount of memory to use. /// private int _memoryUseLimit = 0; // Unlimited /// /// The location of the node exe. This is the full path including the exe file itself. /// private string _nodeExeLocation; /// /// Flag indicating if we should only log critical events. /// private bool _onlyLogCriticalEvents = false; /// /// A list of warnings to treat as errors. /// private ISet _warningsAsErrors; /// /// A list of warnings to treat as low importance messages. /// private ISet _warningsAsMessages; /// /// The location of the toolset definitions. /// private ToolsetDefinitionLocations _toolsetDefinitionLocations = ToolsetDefinitionLocations.Default; /// /// The UI culture. /// private CultureInfo _uiCulture = CultureInfo.CurrentUICulture; /// /// The toolset provider /// private ToolsetProvider _toolsetProvider; /// /// Should the operating environment such as the current directory and environment be saved and restored between project builds and task invocations /// This should be defaulted to true as we should normally do this. This should be set to false for GlobalDTAR which could run at the same time in a different build manager. /// private bool _saveOperatingEnvironment = true; /// /// Should the logging service be done Synchronously when the number of cps's is 1 /// private bool _useSynchronousLogging = false; /// /// Should the inprocess node be shutdown when the build finishes. By default this is false /// since visual studio needs to keep the inprocess node around after the build has finished. /// private bool _shutdownInProcNodeOnBuildFinish = false; /// /// When true, the in-proc node will not be available. /// private bool _disableInProcNode = false; /// /// When true, the build should log task inputs to the loggers. /// private bool _logTaskInputs = false; /// /// When true, the build should log the input parameters. Note - logging these is very expensive! /// private bool _logInitialPropertiesAndItems = false; /// /// The settings used to load the project under build /// private ProjectLoadSettings _projectLoadSettings = ProjectLoadSettings.Default; /// /// Constructor for those who intend to set all properties themselves. /// public BuildParameters() { Initialize(Utilities.GetEnvironmentProperties(), new ProjectRootElementCache(false), null); } /// /// Creates BuildParameters from a ProjectCollection. /// /// The ProjectCollection from which the BuildParameters should populate itself. public BuildParameters(ProjectCollection projectCollection) { ErrorUtilities.VerifyThrowArgumentNull(projectCollection, "projectCollection"); Initialize(new PropertyDictionary(projectCollection.EnvironmentProperties), projectCollection.ProjectRootElementCache, new ToolsetProvider(projectCollection.Toolsets)); _maxNodeCount = projectCollection.MaxNodeCount; _onlyLogCriticalEvents = projectCollection.OnlyLogCriticalEvents; _toolsetDefinitionLocations = projectCollection.ToolsetLocations; _defaultToolsVersion = projectCollection.DefaultToolsVersion; _globalProperties = new PropertyDictionary(projectCollection.GlobalPropertiesCollection); } /// /// Private constructor for translation /// private BuildParameters(INodePacketTranslator translator) { ((INodePacketTranslatable)this).Translate(translator); } /// /// Copy constructor /// private BuildParameters(BuildParameters other) { ErrorUtilities.VerifyThrowInternalNull(other, "other"); _buildId = other._buildId; _culture = other._culture; _defaultToolsVersion = other._defaultToolsVersion; _enableNodeReuse = other._enableNodeReuse; _buildProcessEnvironment = other._buildProcessEnvironment != null ? new Dictionary(other._buildProcessEnvironment) : null; _environmentProperties = other._environmentProperties != null ? new PropertyDictionary(other._environmentProperties) : null; _forwardingLoggers = other._forwardingLoggers != null ? new List(other._forwardingLoggers) : null; _globalProperties = other._globalProperties != null ? new PropertyDictionary(other._globalProperties) : null; _hostServices = other._hostServices; _loggers = other._loggers != null ? new List(other._loggers) : null; _maxNodeCount = other._maxNodeCount; _memoryUseLimit = other._memoryUseLimit; _nodeExeLocation = other._nodeExeLocation; NodeId = other.NodeId; _onlyLogCriticalEvents = other._onlyLogCriticalEvents; #if FEATURE_THREAD_PRIORITY BuildThreadPriority = other.BuildThreadPriority; #endif _toolsetProvider = other._toolsetProvider; _toolsetDefinitionLocations = other._toolsetDefinitionLocations; _toolsetProvider = other._toolsetProvider; _uiCulture = other._uiCulture; _detailedSummary = other._detailedSummary; _shutdownInProcNodeOnBuildFinish = other._shutdownInProcNodeOnBuildFinish; ProjectRootElementCache = other.ProjectRootElementCache; ResetCaches = other.ResetCaches; LegacyThreadingSemantics = other.LegacyThreadingSemantics; _saveOperatingEnvironment = other._saveOperatingEnvironment; _useSynchronousLogging = other._useSynchronousLogging; _disableInProcNode = other._disableInProcNode; _logTaskInputs = other._logTaskInputs; _logInitialPropertiesAndItems = other._logInitialPropertiesAndItems; _warningsAsErrors = other._warningsAsErrors == null ? null : new HashSet(other._warningsAsErrors, StringComparer.OrdinalIgnoreCase); _warningsAsMessages = other._warningsAsMessages == null ? null : new HashSet(other._warningsAsMessages, StringComparer.OrdinalIgnoreCase); _projectLoadSettings = other._projectLoadSettings; } #if FEATURE_THREAD_PRIORITY /// /// Gets or sets the desired thread priority for building. /// public ThreadPriority BuildThreadPriority { get; set; } = ThreadPriority.Normal; #endif /// /// By default if the number of processes is set to 1 we will use Asynchronous logging. However if we want to use synchronous logging when the number of cpu's is set to 1 /// this property needs to be set to true. /// public bool UseSynchronousLogging { get => _useSynchronousLogging; set => _useSynchronousLogging = value; } /// /// Gets the environment variables which were set when this build was created. /// public IDictionary BuildProcessEnvironment => new ReadOnlyDictionary( _buildProcessEnvironment ?? new Dictionary(0)); /// /// The name of the culture to use during the build. /// public CultureInfo Culture { get => _culture; set => _culture = value; } /// /// The default tools version for the build. /// public string DefaultToolsVersion { get => _defaultToolsVersion; set => _defaultToolsVersion = value; } /// /// When true, indicates that the build should emit a detailed summary at the end of the log. /// public bool DetailedSummary { get => _detailedSummary; set => _detailedSummary = value; } /// /// When true, indicates the in-proc node should not be used. /// public bool DisableInProcNode { get => _disableInProcNode; set => _disableInProcNode = value; } /// /// When true, indicates that the task parameters should be logged. /// public bool LogTaskInputs { get => _logTaskInputs; set => _logTaskInputs = value; } /// /// When true, indicates that the initial properties and items should be logged. /// public bool LogInitialPropertiesAndItems { get => _logInitialPropertiesAndItems; set => _logInitialPropertiesAndItems = value; } /// /// Indicates that the build should reset the configuration and results caches. /// public bool ResetCaches { get; set; } /// /// Flag indicating whether out-of-proc nodes should remain after the build and wait for further builds. /// public bool EnableNodeReuse { get => _enableNodeReuse; set => _enableNodeReuse = value; } /// /// Gets an immutable collection of environment properties. /// /// /// This differs from the BuildProcessEnvironment in that there are certain MSBuild-specific properties which are added, and those environment variables which /// would not be valid as MSBuild properties are removed. /// public IDictionary EnvironmentProperties { get { return new ReadOnlyConvertingDictionary(_environmentProperties, instance => ((IProperty) instance).EvaluatedValueEscaped); } } /// /// The collection of forwarding logger descriptions. /// public IEnumerable ForwardingLoggers { get => _forwardingLoggers; set { if (value != null) { foreach (ForwardingLoggerRecord logger in value) { ErrorUtilities.VerifyThrowArgumentNull(logger, "ForwardingLoggers", "NullLoggerNotAllowed"); } } _forwardingLoggers = value; } } /// /// Sets or retrieves an immutable collection of global properties. /// [SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly", Justification = "Accessor returns a readonly collection, and the BuildParameters class is immutable.")] public IDictionary GlobalProperties { get { return new ReadOnlyConvertingDictionary(_globalProperties, instance => ((IProperty) instance).EvaluatedValueEscaped); } set { _globalProperties = new PropertyDictionary(value.Count); foreach (KeyValuePair property in value) { _globalProperties[property.Key] = ProjectPropertyInstance.Create(property.Key, property.Value); } } } /// /// Interface allowing the host to provide additional control over the build process. /// public HostServices HostServices { get => _hostServices; set => _hostServices = value; } /// /// Enables or disables legacy threading semantics /// /// /// Legacy threading semantics indicate that if a submission is to be built /// only on the in-proc node and the submission is executed synchronously, then all of its /// requests will be built on the thread which invoked the build rather than a /// thread owned by the BuildManager. /// public bool LegacyThreadingSemantics { get; set; } /// /// The collection of loggers to use during the build. /// public IEnumerable Loggers { get => _loggers; set { if (value != null) { foreach (ILogger logger in value) { ErrorUtilities.VerifyThrowArgumentNull(logger, "Loggers", "NullLoggerNotAllowed"); } } _loggers = value; } } /// /// The maximum number of nodes this build may use. /// public int MaxNodeCount { get => _maxNodeCount; set { ErrorUtilities.VerifyThrowArgument(value > 0, "InvalidMaxNodeCount"); _maxNodeCount = value; } } /// /// The amount of memory the build should limit itself to using, in megabytes. /// public int MemoryUseLimit { get => _memoryUseLimit; set => _memoryUseLimit = value; } /// /// The location of the build node executable. /// public string NodeExeLocation { get => _nodeExeLocation; set => _nodeExeLocation = value; } /// /// Flag indicating if non-critical logging events should be discarded. /// public bool OnlyLogCriticalEvents { get => _onlyLogCriticalEvents; set => _onlyLogCriticalEvents = value; } /// /// A list of warnings to treat as errors. To treat all warnings as errors, set this to an empty . /// public ISet WarningsAsErrors { get => _warningsAsErrors; set => _warningsAsErrors = value; } /// /// A list of warnings to treat as low importance messages. /// public ISet WarningsAsMessages { get => _warningsAsMessages; set => _warningsAsMessages = value; } /// /// Locations to search for toolsets. /// public ToolsetDefinitionLocations ToolsetDefinitionLocations { get => _toolsetDefinitionLocations; set => _toolsetDefinitionLocations = value; } /// /// Returns all of the toolsets. /// /// /// toolsetProvider.Toolsets is already a readonly collection. /// public ICollection Toolsets => _toolsetProvider.Toolsets; /// /// The name of the UI culture to use during the build. /// public CultureInfo UICulture { get => _uiCulture; set => _uiCulture = value; } /// /// Flag indicating if the operating environment such as the current directory and environment be saved and restored between project builds and task invocations. /// This should be set to false for any other build managers running in the system so that we do not have two build managers trampling on each others environment. /// public bool SaveOperatingEnvironment { get => _saveOperatingEnvironment; set => _saveOperatingEnvironment = value; } /// /// Shutdown the inprocess node when the build finishes. By default this is false /// since visual studio needs to keep the inprocess node around after the build finishes. /// public bool ShutdownInProcNodeOnBuildFinish { get => _shutdownInProcNodeOnBuildFinish; set => _shutdownInProcNodeOnBuildFinish = value; } /// /// Gets the internal msbuild thread stack size. /// internal static int ThreadStackSize => CommunicationsUtilities.GetIntegerVariableOrDefault( "MSBUILDTHREADSTACKSIZE", DefaultThreadStackSize); /// /// Gets the endpoint shutdown timeout. /// internal static int EndpointShutdownTimeout => CommunicationsUtilities.GetIntegerVariableOrDefault( "MSBUILDENDPOINTSHUTDOWNTIMEOUT", DefaultEndpointShutdownTimeout); /// /// Gets or sets the engine shutdown timeout. /// internal static int EngineShutdownTimeout => CommunicationsUtilities.GetIntegerVariableOrDefault( "MSBUILDENGINESHUTDOWNTIMEOUT", DefaultEngineShutdownTimeout); /// /// Gets the maximum number of idle request builders to retain. /// internal static int IdleRequestBuilderLimit => GetStaticIntVariableOrDefault("MSBUILDIDLEREQUESTBUILDERLIMIT", ref s_idleRequestBuilderLimit, DefaultIdleRequestBuilderLimit); /// /// Gets the logging thread shutdown timeout. /// internal static int LoggingThreadShutdownTimeout => CommunicationsUtilities.GetIntegerVariableOrDefault( "MSBUILDLOGGINGTHREADSHUTDOWNTIMEOUT", DefaultLoggingThreadShutdownTimeout); /// /// Gets the request builder shutdown timeout. /// internal static int RequestBuilderShutdownTimeout => CommunicationsUtilities.GetIntegerVariableOrDefault( "MSBUILDREQUESTBUILDERSHUTDOWNTIMEOUT", DefaultRequestBuilderShutdownTimeout); /// /// Gets the startup directory. /// internal static string StartupDirectory => s_startupDirectory; /// /// Indicates whether the build plan is enabled or not. /// internal static bool EnableBuildPlan => GetStaticBoolVariableOrDefault("MSBUILDENABLEBUILDPLAN", ref s_enableBuildPlan, false); /// /// Indicates whether we should warn when a property is uninitialized when it is used. /// internal static bool WarnOnUninitializedProperty { get => GetStaticBoolVariableOrDefault("MSBUILDWARNONUNINITIALIZEDPROPERTY", ref s_warnOnUninitializedProperty, false); set => s_warnOnUninitializedProperty = value; } /// /// Indicates whether we should dump string interning stats /// internal static bool DumpOpportunisticInternStats => GetStaticBoolVariableOrDefault( "MSBUILDDUMPOPPORTUNISTICINTERNSTATS", ref s_dumpOpportunisticInternStats, false); /// /// Indicates whether we should dump debugging information about the expander /// internal static bool DebugExpansion => GetStaticBoolVariableOrDefault("MSBUILDDEBUGEXPANSION", ref s_debugExpansion, false); /// /// Indicates whether we should keep duplicate target outputs /// internal static bool KeepDuplicateOutputs => GetStaticBoolVariableOrDefault("MSBUILDKEEPDUPLICATEOUTPUTS", ref s_keepDuplicateOutputs, false); /// /// Gets or sets the build id. /// internal int BuildId { get => _buildId; set => _buildId = value; } /// /// Gets or sets the environment properties. /// /// /// This is not the same as BuildProcessEnvironment. See EnvironmentProperties. These properties are those which /// are used during evaluation of a project, and exclude those properties which would not be valid MSBuild properties /// because they contain invalid characters (such as 'Program Files (x86)'). /// internal PropertyDictionary EnvironmentPropertiesInternal { get => _environmentProperties; set { ErrorUtilities.VerifyThrowInternalNull(value, "EnvironmentPropertiesInternal"); _environmentProperties = value; } } /// /// Gets the global properties. /// internal PropertyDictionary GlobalPropertiesInternal => _globalProperties; /// /// Gets or sets the node id. /// internal int NodeId { get; set; } = 0; /// /// Gets the toolset provider. /// internal IToolsetProvider ToolsetProvider { get { EnsureToolsets(); return _toolsetProvider; } } /// /// The one and only project root element cache to be used for the build. /// internal ProjectRootElementCache ProjectRootElementCache { get; set; } #if FEATURE_APPDOMAIN /// /// Information for configuring child AppDomains. /// internal AppDomainSetup AppDomainSetup { get; set; } #endif /// /// (for diagnostic use) Whether or not this is out of proc /// internal bool IsOutOfProc { get; set; } /// public ProjectLoadSettings ProjectLoadSettings { get => _projectLoadSettings; set => _projectLoadSettings = value; } /// /// Retrieves a toolset. /// public Toolset GetToolset(string toolsVersion) { EnsureToolsets(); return _toolsetProvider.GetToolset(toolsVersion); } /// /// Creates a clone of this BuildParameters object. This creates a clone of the logger collections, but does not deep clone /// the loggers within. /// public BuildParameters Clone() { return new BuildParameters(this); } /// /// Implementation of the serialization mechanism. /// void INodePacketTranslatable.Translate(INodePacketTranslator translator) { translator.Translate(ref _buildId); /* No build thread priority during translation. We specifically use the default (which is ThreadPriority.Normal) */ translator.TranslateDictionary(ref _buildProcessEnvironment, StringComparer.OrdinalIgnoreCase); translator.TranslateCulture(ref _culture); translator.Translate(ref _defaultToolsVersion); translator.Translate(ref _disableInProcNode); translator.Translate(ref _enableNodeReuse); translator.TranslateProjectPropertyInstanceDictionary(ref _environmentProperties); /* No forwarding logger information sent here - that goes with the node configuration */ translator.TranslateProjectPropertyInstanceDictionary(ref _globalProperties); /* No host services during translation */ /* No loggers during translation */ translator.Translate(ref _maxNodeCount); translator.Translate(ref _memoryUseLimit); translator.Translate(ref _nodeExeLocation); /* No node id during translation */ translator.Translate(ref _onlyLogCriticalEvents); translator.Translate(ref s_startupDirectory); translator.TranslateCulture(ref _uiCulture); translator.Translate(ref _toolsetProvider, Microsoft.Build.Evaluation.ToolsetProvider.FactoryForDeserialization); translator.Translate(ref _useSynchronousLogging); translator.Translate(ref _shutdownInProcNodeOnBuildFinish); translator.Translate(ref _logTaskInputs); translator.Translate(ref _logInitialPropertiesAndItems); translator.TranslateEnum(ref _projectLoadSettings, (int) _projectLoadSettings); // ProjectRootElementCache is not transmitted. // ResetCaches is not transmitted. // LegacyThreadingSemantics is not transmitted. } #region INodePacketTranslatable Members /// /// The class factory for deserialization. /// internal static BuildParameters FactoryForDeserialization(INodePacketTranslator translator) { return new BuildParameters(translator); } #endregion /// /// Gets the value of a boolean environment setting which is not expected to change. /// private static bool GetStaticBoolVariableOrDefault(string environmentVariable, ref bool? backing, bool @default) { if (!backing.HasValue) { backing = !String.IsNullOrEmpty(Environment.GetEnvironmentVariable(environmentVariable)) || @default; } return backing.Value; } /// /// Gets the value of an integer environment variable, or returns the default if none is set or it cannot be converted. /// private static int GetStaticIntVariableOrDefault(string environmentVariable, ref int? backingValue, int defaultValue) { if (!backingValue.HasValue) { string environmentValue = Environment.GetEnvironmentVariable(environmentVariable); if (String.IsNullOrEmpty(environmentValue)) { backingValue = defaultValue; } else { backingValue = Int32.TryParse(environmentValue, out var parsedValue) ? parsedValue : defaultValue; } } return backingValue.Value; } /// /// Centralization of the common parts of construction. /// private void Initialize(PropertyDictionary environmentProperties, ProjectRootElementCache projectRootElementCache, ToolsetProvider toolsetProvider) { _buildProcessEnvironment = CommunicationsUtilities.GetEnvironmentVariables(); _environmentProperties = environmentProperties; ProjectRootElementCache = projectRootElementCache; ResetCaches = true; _toolsetProvider = toolsetProvider; if (Environment.GetEnvironmentVariable("MSBUILDDISABLENODEREUSE") == "1") // For example to disable node reuse within Visual Studio { _enableNodeReuse = false; } if (Environment.GetEnvironmentVariable("MSBUILDDETAILEDSUMMARY") == "1") // For example to get detailed summary within Visual Studio { _detailedSummary = true; } _nodeExeLocation = FindMSBuildExe(); } /// /// Loads the toolsets if we don't have them already. /// private void EnsureToolsets() { if (_toolsetProvider != null) { return; } _toolsetProvider = new ToolsetProvider(DefaultToolsVersion, _environmentProperties, _globalProperties, ToolsetDefinitionLocations); } /// /// This method determines where MSBuild.Exe is and sets the NodeExePath to that by default. /// private string FindMSBuildExe() { string location = _nodeExeLocation; // Use the location specified by the user in code. if (!string.IsNullOrEmpty(location) && CheckMSBuildExeExistsAt(location)) { return location; } // Try what we think is the current executable path. return BuildEnvironmentHelper.Instance.CurrentMSBuildExePath; } /// /// Helper to avoid doing an expensive disk check for MSBuild.exe when /// we already checked in a previous build. /// This File.Exists otherwise can show up in profiles when there's a lot of /// design time builds going on. /// private bool CheckMSBuildExeExistsAt(string path) { if (s_msbuildExeKnownToExistAt != null && string.Equals(path, s_msbuildExeKnownToExistAt, StringComparison.OrdinalIgnoreCase)) { // We found it there last time: it must exist there. return true; } if (File.Exists(path)) { s_msbuildExeKnownToExistAt = path; return true; } return false; } } }