internal static bool BuildProject ( string projectFile, string[] targets, string toolsVersion, Dictionary<string, string> globalProperties, ILogger[] loggers, LoggerVerbosity verbosity, DistributedLoggerRecord[] distributedLoggerRecords, bool needToValidateProject, string schemaFile, int cpuCount, bool enableNodeReuse, TextWriter preprocessWriter, bool debugger, bool detailedSummary ) { if (String.Equals(Path.GetExtension(projectFile), ".vcproj", StringComparison.OrdinalIgnoreCase) || String.Equals(Path.GetExtension(projectFile), ".dsp", StringComparison.OrdinalIgnoreCase)) { InitializationException.Throw(ResourceUtilities.FormatResourceString("ProjectUpgradeNeededToVcxProj", projectFile), null); } bool success = false; ProjectCollection projectCollection = null; bool onlyLogCriticalEvents = false; try { List<ForwardingLoggerRecord> remoteLoggerRecords = new List<ForwardingLoggerRecord>(); foreach (DistributedLoggerRecord distRecord in distributedLoggerRecords) { remoteLoggerRecords.Add(new ForwardingLoggerRecord(distRecord.CentralLogger, distRecord.ForwardingLoggerDescription)); } // Targeted perf optimization for the case where we only have our own parallel console logger, and verbosity is quiet. In such a case // we know we won't emit any messages except for errors and warnings, so the engine should not bother even logging them. // If we're using the original serial console logger we can't do this, as it shows project started/finished context // around errors and warnings. // Telling the engine to not bother logging non-critical messages means that typically it can avoid loading any resources in the successful // build case. if (loggers.Length == 1 && remoteLoggerRecords.Count == 0 && verbosity == LoggerVerbosity.Quiet && loggers[0].Parameters != null && loggers[0].Parameters.IndexOf("ENABLEMPLOGGING", StringComparison.OrdinalIgnoreCase) != -1 && loggers[0].Parameters.IndexOf("DISABLEMPLOGGING", StringComparison.OrdinalIgnoreCase) == -1 && loggers[0].Parameters.IndexOf("V=", StringComparison.OrdinalIgnoreCase) == -1 && // Console logger could have had a verbosity loggers[0].Parameters.IndexOf("VERBOSITY=", StringComparison.OrdinalIgnoreCase) == -1) // override with the /clp switch { // Must be exactly the console logger, not a derived type like the file logger. Type t1 = loggers[0].GetType(); Type t2 = typeof(ConsoleLogger); if (t1 == t2) { onlyLogCriticalEvents = true; } } // HACK HACK: this enables task parameter logging. // This is a hack for now to make sure the perf hit only happens // on diagnostic. This should be changed to pipe it through properly, // perhaps as part of a fuller tracing feature. bool logTaskInputs = verbosity == LoggerVerbosity.Diagnostic; if (!logTaskInputs) { foreach (var logger in loggers) { if (logger.Parameters != null && (logger.Parameters.IndexOf("V=DIAG", StringComparison.OrdinalIgnoreCase) != -1 || logger.Parameters.IndexOf("VERBOSITY=DIAG", StringComparison.OrdinalIgnoreCase) != -1) ) { logTaskInputs = true; break; } } } if (!logTaskInputs) { foreach (var logger in distributedLoggerRecords) { if (logger.CentralLogger != null) { if (logger.CentralLogger.Parameters != null && (logger.CentralLogger.Parameters.IndexOf("V=DIAG", StringComparison.OrdinalIgnoreCase) != -1 || logger.CentralLogger.Parameters.IndexOf("VERBOSITY=DIAG", StringComparison.OrdinalIgnoreCase) != -1) ) { logTaskInputs = true; break; } } } } projectCollection = new ProjectCollection ( globalProperties, loggers, null, Microsoft.Build.Evaluation.ToolsetDefinitionLocations.ConfigurationFile | Microsoft.Build.Evaluation.ToolsetDefinitionLocations.Registry, cpuCount, onlyLogCriticalEvents ); if (debugger) { // Debugging is not currently fully supported so we don't want to open // public API for it. Also, we want to have a way to make it work when running inside VS. // So use an environment variable. The undocumented /debug switch is just an easy way to set it. Environment.SetEnvironmentVariable("MSBUILDDEBUGGING", "1"); } if (toolsVersion != null && !projectCollection.ContainsToolset(toolsVersion)) { ThrowInvalidToolsVersionInitializationException(projectCollection.Toolsets, toolsVersion); } // If the user has requested that the schema be validated, do that here. if (needToValidateProject && !FileUtilities.IsSolutionFilename(projectFile)) { Microsoft.Build.Evaluation.Project project = projectCollection.LoadProject(projectFile, globalProperties, toolsVersion); Microsoft.Build.Evaluation.Toolset toolset = projectCollection.GetToolset((toolsVersion == null) ? project.ToolsVersion : toolsVersion); if (toolset == null) { ThrowInvalidToolsVersionInitializationException(projectCollection.Toolsets, project.ToolsVersion); } ProjectSchemaValidationHandler.VerifyProjectSchema(projectFile, schemaFile, toolset.ToolsPath); // If there are schema validation errors, an InitializationException is thrown, so if we get here, // we can safely assume that the project successfully validated. projectCollection.UnloadProject(project); } if (preprocessWriter != null && !FileUtilities.IsSolutionFilename(projectFile)) { Project project = projectCollection.LoadProject(projectFile, globalProperties, toolsVersion); project.SaveLogicalProject(preprocessWriter); projectCollection.UnloadProject(project); success = true; } else { BuildRequestData request = new BuildRequestData(projectFile, globalProperties, toolsVersion, targets, null); BuildParameters parameters = new BuildParameters(projectCollection); // By default we log synchronously to the console for compatibility with previous versions, // but it is slightly slower if (!String.Equals(Environment.GetEnvironmentVariable("MSBUILDLOGASYNC"), "1", StringComparison.Ordinal)) { parameters.UseSynchronousLogging = true; } parameters.EnableNodeReuse = enableNodeReuse; parameters.NodeExeLocation = Assembly.GetExecutingAssembly().Location; parameters.MaxNodeCount = cpuCount; parameters.Loggers = projectCollection.Loggers; parameters.ForwardingLoggers = remoteLoggerRecords; parameters.ToolsetDefinitionLocations = Microsoft.Build.Evaluation.ToolsetDefinitionLocations.ConfigurationFile | Microsoft.Build.Evaluation.ToolsetDefinitionLocations.Registry; parameters.DetailedSummary = detailedSummary; parameters.LogTaskInputs = logTaskInputs; if (!String.IsNullOrEmpty(toolsVersion)) { parameters.DefaultToolsVersion = toolsVersion; } string memoryUseLimit = Environment.GetEnvironmentVariable("MSBUILDMEMORYUSELIMIT"); if (!String.IsNullOrEmpty(memoryUseLimit)) { parameters.MemoryUseLimit = Convert.ToInt32(memoryUseLimit, CultureInfo.InvariantCulture); // The following ensures that when we divide the use by node count to get the per-limit amount, we always end up with a // positive value - otherwise setting it too low will result in a zero, which will enable only the default cache behavior // which is not what is intended by using this environment variable. if (parameters.MemoryUseLimit < parameters.MaxNodeCount) { parameters.MemoryUseLimit = parameters.MaxNodeCount; } } BuildManager buildManager = BuildManager.DefaultBuildManager; #if MSBUILDENABLEVSPROFILING DataCollection.CommentMarkProfile(8800, "Pending Build Request from MSBuild.exe"); #endif BuildResult results = null; buildManager.BeginBuild(parameters); Exception exception = null; try { try { lock (s_buildLock) { s_activeBuild = buildManager.PendBuildRequest(request); // Even if Ctrl-C was already hit, we still pend the build request and then cancel. // That's so the build does not appear to have completed successfully. if (s_receivedCancel == 1) { buildManager.CancelAllSubmissions(); } } results = s_activeBuild.Execute(); } finally { buildManager.EndBuild(); } } catch (Exception ex) { exception = ex; success = false; } if (results != null && exception == null) { success = results.OverallResult == BuildResultCode.Success; exception = results.Exception; } if (exception != null) { success = false; // InvalidProjectFileExceptions have already been logged. if (exception.GetType() != typeof(InvalidProjectFileException)) { if ( exception.GetType() == typeof(LoggerException) || exception.GetType() == typeof(InternalLoggerException) ) { // We will rethrow this so the outer exception handler can catch it, but we don't // want to log the outer exception stack here. throw exception; } if (exception.GetType() == typeof(BuildAbortedException)) { // this is not a bug and should not dump stack. It will already have been logged // appropriately, there is no need to take any further action with it. } else { // After throwing again below the stack will be reset. Make certain we log everything we // can now Console.WriteLine(AssemblyResources.GetString("FatalError")); #if DEBUG Console.WriteLine("This is an unhandled exception in MSBuild -- PLEASE OPEN A BUG AGAINST THE MSBUILD TEAM."); #endif Console.WriteLine(exception.ToString()); Console.WriteLine(); throw exception; } } } } } // handle project file errors catch (InvalidProjectFileException ex) { // just eat the exception because it has already been logged ErrorUtilities.VerifyThrow(ex.HasBeenLogged, "Should have been logged"); success = false; } finally { FileUtilities.ClearCacheDirectory(); if (projectCollection != null) { projectCollection.Dispose(); } BuildManager.DefaultBuildManager.Dispose(); } return success; }
/// <summary> /// Parses command line arguments describing the distributed loggers /// </summary> /// <returns>List of distributed logger records</returns> private static List<DistributedLoggerRecord> ProcessDistributedLoggerSwitch(string[] parameters, LoggerVerbosity verbosity) { List<DistributedLoggerRecord> distributedLoggers = new List<DistributedLoggerRecord>(); foreach (string parameter in parameters) { // split each <central logger>|<node logger> string into two pieces, breaking on the first | that is found int emptySplits; // ignored ArrayList loggerSpec = QuotingUtilities.SplitUnquoted(parameter, 2, true /* keep empty splits */, false /* keep quotes */, out emptySplits, '*'); ErrorUtilities.VerifyThrow((loggerSpec.Count >= 1) && (loggerSpec.Count <= 2), "SplitUnquoted() must return at least one string, and no more than two."); string unquotedParameter = QuotingUtilities.Unquote((string)loggerSpec[0]); LoggerDescription centralLoggerDescription = ParseLoggingParameter((string)loggerSpec[0], unquotedParameter, verbosity); ILogger centralLogger = CreateAndConfigureLogger(centralLoggerDescription, verbosity, unquotedParameter); // By default if no forwarding logger description is specified the same logger is used for both functions LoggerDescription forwardingLoggerDescription = centralLoggerDescription; if (loggerSpec.Count > 1) { unquotedParameter = QuotingUtilities.Unquote((string)loggerSpec[1]); forwardingLoggerDescription = ParseLoggingParameter((string)loggerSpec[1], unquotedParameter, verbosity); } DistributedLoggerRecord distributedLoggerRecord = new DistributedLoggerRecord(centralLogger, forwardingLoggerDescription); distributedLoggers.Add(distributedLoggerRecord); } return distributedLoggers; }
/// <summary> /// Returns a DistributedLoggerRecord containing this logger and a ConfigurableForwardingLogger. /// Looks at the logger's parameters for any verbosity parameter in order to make sure it is setting up the ConfigurableForwardingLogger /// with the verbosity level that the logger will actually use. /// </summary> private static DistributedLoggerRecord CreateForwardingLoggerRecord(ILogger logger, string loggerParameters, LoggerVerbosity defaultVerbosity) { string verbosityParameter = ExtractAnyLoggerParameter(loggerParameters, "verbosity", "v"); string verbosityValue = ExtractAnyParameterValue(verbosityParameter); LoggerVerbosity effectiveVerbosity = defaultVerbosity; if (!String.IsNullOrEmpty(verbosityValue)) { effectiveVerbosity = ProcessVerbositySwitch(verbosityValue); } //Gets the currently loaded assembly in which the specified class is defined Assembly engineAssembly = Assembly.GetAssembly(typeof(ProjectCollection)); string loggerClassName = "Microsoft.Build.Logging.ConfigurableForwardingLogger"; string loggerAssemblyName = engineAssembly.GetName().FullName; LoggerDescription forwardingLoggerDescription = new LoggerDescription(loggerClassName, loggerAssemblyName, null, loggerParameters, effectiveVerbosity); DistributedLoggerRecord distributedLoggerRecord = new DistributedLoggerRecord(logger, forwardingLoggerDescription); return distributedLoggerRecord; }
/// <summary> /// Process the file logger switches and attach the correct file loggers. Internal for testing /// </summary> internal static void ProcessDistributedFileLogger ( bool distributedFileLogger, string[] fileLoggerParameters, List<DistributedLoggerRecord> distributedLoggerRecords, ArrayList loggers, int cpuCount ) { if (distributedFileLogger) { string fileParameters = string.Empty; if ((fileLoggerParameters != null) && (fileLoggerParameters.Length > 0)) { // Join the file logger parameters into one string seperated by semicolons fileParameters = AggregateParameters(null, fileLoggerParameters); } // Check to see if the logfile parameter has been set, if not set it to the current directory string logFileParameter = ExtractAnyLoggerParameter(fileParameters, "logfile"); string logFileName = ExtractAnyParameterValue(logFileParameter); try { // If the path is not an absolute path set the path to the current directory of the exe combined with the relative path // If the string is empty then send it through as the distributed file logger WILL deal with EMPTY logfile paths if (!String.IsNullOrEmpty(logFileName) && !Path.IsPathRooted(logFileName)) { fileParameters = fileParameters.Replace(logFileParameter, "logFile=" + Path.Combine(Directory.GetCurrentDirectory(), logFileName)); } } catch (Exception e) { if (ExceptionHandling.NotExpectedException(e)) { throw; } throw new LoggerException(e.Message, e); } if (String.IsNullOrEmpty(logFileName)) { // If the string is not empty and it does not end in a ;, we need to add a ; to seperate what is in the parameter from the logfile // if the string is empty, no ; is needed because logfile is the only parameter which will be passed in if (!String.IsNullOrEmpty(fileParameters) && !fileParameters.EndsWith(";", StringComparison.OrdinalIgnoreCase)) { fileParameters += ";"; } fileParameters += "logFile=" + Path.Combine(Directory.GetCurrentDirectory(), msbuildLogFileName); } //Gets the currently loaded assembly in which the specified class is defined Assembly engineAssembly = Assembly.GetAssembly(typeof(ProjectCollection)); string loggerClassName = "Microsoft.Build.Logging.DistributedFileLogger"; string loggerAssemblyName = engineAssembly.GetName().FullName; // Node the verbosity parameter is not used by the Distributed file logger so changing it here has no effect. It must be changed in the distributed file logger LoggerDescription forwardingLoggerDescription = new LoggerDescription(loggerClassName, loggerAssemblyName, null, fileParameters, LoggerVerbosity.Detailed); // Use the null as the central Logger, this will cause the engine to instantiate the NullCentralLogger, this logger will throw an exception if anything except for the buildstarted and buildFinished events are sent DistributedLoggerRecord distributedLoggerRecord = new DistributedLoggerRecord(null, forwardingLoggerDescription); distributedLoggerRecords.Add(distributedLoggerRecord); } }
private static bool BuildProjectWithOldOM(string projectFile, string[] targets, string toolsVersion, Microsoft.Build.BuildEngine.BuildPropertyGroup propertyBag, ILogger[] loggers, LoggerVerbosity verbosity, DistributedLoggerRecord[] distributedLoggerRecords, bool needToValidateProject, string schemaFile, int cpuCount) { string msbuildLocation = Path.GetDirectoryName(Assembly.GetAssembly(typeof(MSBuildApp)).Location); string localNodeProviderParameters = "msbuildlocation=" + msbuildLocation; /*This assembly is the exe*/ ; localNodeProviderParameters += ";nodereuse=false"; Microsoft.Build.BuildEngine.Engine engine = new Microsoft.Build.BuildEngine.Engine(propertyBag, Microsoft.Build.BuildEngine.ToolsetDefinitionLocations.ConfigurationFile | Microsoft.Build.BuildEngine.ToolsetDefinitionLocations.Registry, cpuCount, localNodeProviderParameters); bool success = false; try { foreach (ILogger logger in loggers) { engine.RegisterLogger(logger); } // Targeted perf optimization for the case where we only have our own parallel console logger, and verbosity is quiet. In such a case // we know we won't emit any messages except for errors and warnings, so the engine should not bother even logging them. // If we're using the original serial console logger we can't do this, as it shows project started/finished context // around errors and warnings. // Telling the engine to not bother logging non-critical messages means that typically it can avoid loading any resources in the successful // build case. if (loggers.Length == 1 && verbosity == LoggerVerbosity.Quiet && loggers[0].Parameters.IndexOf("ENABLEMPLOGGING", StringComparison.OrdinalIgnoreCase) != -1 && loggers[0].Parameters.IndexOf("DISABLEMPLOGGING", StringComparison.OrdinalIgnoreCase) == -1 && loggers[0].Parameters.IndexOf("V=", StringComparison.OrdinalIgnoreCase) == -1 && // Console logger could have had a verbosity loggers[0].Parameters.IndexOf("VERBOSITY=", StringComparison.OrdinalIgnoreCase) == -1) // override with the /clp switch { // Must be exactly the console logger, not a derived type like the file logger. Type t1 = loggers[0].GetType(); Type t2 = typeof(ConsoleLogger); if (t1 == t2) { engine.OnlyLogCriticalEvents = true; } } Microsoft.Build.BuildEngine.Project project = null; try { project = new Microsoft.Build.BuildEngine.Project(engine, toolsVersion); } catch (InvalidOperationException e) { InitializationException.Throw("InvalidToolsVersionError", toolsVersion, e, false /*no stack*/); } project.IsValidated = needToValidateProject; project.SchemaFile = schemaFile; project.Load(projectFile); success = engine.BuildProject(project, targets); } // handle project file errors catch (InvalidProjectFileException) { // just eat the exception because it has already been logged } finally { // Unregister loggers and finish with engine engine.Shutdown(); } return success; }