/// <summary> /// Given a task name, this method retrieves the task class. If the task has been requested before, it will be found in /// the class cache; otherwise, <UsingTask> declarations will be used to search the appropriate assemblies. /// </summary> /// <param name="taskName"></param> /// <param name="taskProjectFile"></param> /// <param name="taskNode"></param> /// <param name="exactMatchRequired"></param> /// <param name="loggingServices"></param> /// <param name="buildEventContext"></param> /// <param name="taskClass"></param> /// <returns>true, if task is found</returns> public bool GetRegisteredTask ( string taskName, string taskProjectFile, XmlNode taskNode, bool exactMatchRequired, EngineLoggingServices loggingServices, BuildEventContext buildEventContext, out LoadedType taskClass ) { taskClass = null; // If there are no using tags in the project don't bother caching or looking for tasks if (registeredTasks == null) { return(false); } Hashtable cachedTaskClasses = exactMatchRequired ? this.cachedTaskClassesWithExactMatch : this.cachedTaskClassesWithFuzzyMatch; if (cachedTaskClasses.Contains(taskName)) { // Caller has asked us before for this same task name, and for the same value of "bool exactMatchRequired". // Return whatever the previous result was, even if it was null. Why would the result be different than // it was before? NOTE: Hash tables CAN have "null" as their value, and this still returns "true" for Contains(...). taskClass = (LoadedType)cachedTaskClasses[taskName]; } else { Hashtable registeredTasksFound; // look for the given task name in the registry; if not found, gather all registered task names that partially // match the given name if (FindRegisteredTasks(taskName, exactMatchRequired, out registeredTasksFound)) { foreach (DictionaryEntry registeredTaskFound in registeredTasksFound) { string mostSpecificTaskName = (string)registeredTaskFound.Key; // if the given task name is longer than the registered task name if (taskName.Length > ((string)registeredTaskFound.Key).Length) { // we will use the longer name to help disambiguate between multiple matches mostSpecificTaskName = taskName; } if (GetTaskFromAssembly(mostSpecificTaskName, (ArrayList)registeredTaskFound.Value, taskProjectFile, taskNode, loggingServices, buildEventContext, out taskClass)) { // Whilst we are within the processing of the task, we haven't actually started executing it, so // our using task message needs to be in the context of the target. However any errors should be reported // at the point where the task appears in the project. BuildEventContext usingTaskContext = new BuildEventContext(buildEventContext.NodeId, buildEventContext.TargetId, buildEventContext.ProjectContextId, BuildEventContext.InvalidTaskId); loggingServices.LogComment(usingTaskContext, "TaskFound", taskName, taskClass.Assembly.ToString()); break; } } } // Cache the result, even if it is null. We should never again do the work we just did, for this task name. cachedTaskClasses[taskName] = taskClass; } return(taskClass != null); }
/// <summary> /// Stub implementation -- forwards to engine being proxied. /// </summary> public void LogErrorEvent(BuildErrorEventArgs e) { ErrorUtilities.VerifyThrowArgumentNull(e, nameof(e)); ErrorUtilities.VerifyThrowInvalidOperation(activeProxy, "AttemptingToLogFromInactiveTask"); if (parentModule.IsRunningMultipleNodes && !e.GetType().IsSerializable) { loggingServices.LogWarning(buildEventContext, new BuildEventFileInfo(string.Empty), "ExpectedEventToBeSerializable", e.GetType().Name); return; } string message = GetUpdatedMessage(e.File, e.Message, parentProjectFullFileName); if (ContinueOnError) { // Convert the error into a warning. We do this because the whole point of // ContinueOnError is that a project author expects that the task might fail, // but wants to ignore the failures. This implies that we shouldn't be logging // errors either, because you should never have a successful build with errors. BuildWarningEventArgs warningEvent = new BuildWarningEventArgs (e.Subcategory, e.Code, e.File, e.LineNumber, e.ColumnNumber, e.EndLineNumber, e.EndColumnNumber, message, // this is the new message from above e.HelpKeyword, e.SenderName); warningEvent.BuildEventContext = buildEventContext; loggingServices.LogWarningEvent(warningEvent); // Log a message explaining why we converted the previous error into a warning. loggingServices.LogComment(buildEventContext, MessageImportance.Normal, "ErrorConvertedIntoWarning"); } else { if (e.GetType().Equals(BuildErrorEventArgsType)) { // We'd like to add the project file to the subcategory, but since this property // is read-only on the BuildErrorEventArgs type, this requires creating a new // instance. However, if some task logged a custom error type, we don't want to // impolitely (as we already do above on ContinueOnError) throw the custom type // data away. e = new BuildErrorEventArgs ( e.Subcategory, e.Code, e.File, e.LineNumber, e.ColumnNumber, e.EndLineNumber, e.EndColumnNumber, message, // this is the new message from above e.HelpKeyword, e.SenderName ); } e.BuildEventContext = buildEventContext; loggingServices.LogErrorEvent(e); } }
/// <summary> /// Validates a project against the given schema -- if no schema is provided, uses the default schema. /// </summary> /// <owner>JomoF</owner> /// <param name="contentReader"></param> /// <param name="schemaFile">Can be null.</param> /// <param name="projectFile"></param> private void VerifyProjectSchema ( TextReader contentReader, string schemaFile, string projectFile ) { // Options for XmlReader object can be set only in constructor. After the object is created, they // become read-only. Because of that we need to create // XmlSettings structure, fill it in with correct parameters and pass into XmlReader constructor. XmlReaderSettings validatorSettings = new XmlReaderSettings(); validatorSettings.ValidationType = ValidationType.Schema; validatorSettings.XmlResolver = null; validatorSettings.ValidationEventHandler += this.OnSchemaValidationError; if (string.IsNullOrEmpty(schemaFile)) { schemaFile = Path.Combine(binPath, "Microsoft.Build.xsd"); } // Log the schema file we're using, particularly since it can vary // according to the toolset being used engineLoggingServices.LogComment(buildEventContext, "SchemaFileLocation", schemaFile); XmlTextReader schemaReader = new XmlTextReader(schemaFile); schemaReader.DtdProcessing = DtdProcessing.Ignore; using (schemaReader) { try { validatorSettings.Schemas.Add(XMakeAttributes.defaultXmlNamespace, schemaReader); // We need full path to the project file to be able handle it as URI in ValidationEventHandler. // Uri class cannot instantiate with relative paths. if (projectFile.Length != 0) { projectFile = Path.GetFullPath(projectFile); } using (XmlReader validator = XmlReader.Create(contentReader, validatorSettings, projectFile)) // May also throw XmlSchemaException { this.syntaxError = false; bool couldRead = true; while (couldRead) { try { couldRead = validator.Read(); } catch (XmlException) { // We swallow exception here because XmlValidator fires the validation event to report the error // And we handle the event. Also XmlValidator can continue parsing Xml text after throwing an exception. // Thus we don't need any special recover here. } } ProjectFileErrorUtilities.VerifyThrowInvalidProjectFile(!this.syntaxError, "SubCategoryForSchemaValidationErrors", new BuildEventFileInfo(projectFile), "ProjectSchemaErrorHalt"); } } // handle errors in the schema itself catch (XmlException e) { ProjectFileErrorUtilities.VerifyThrowInvalidProjectFile(false, "SubCategoryForSchemaValidationErrors", new BuildEventFileInfo(e), "InvalidSchemaFile", schemaFile, e.Message); } // handle errors in the schema itself catch (XmlSchemaException e) { ProjectFileErrorUtilities.VerifyThrowInvalidProjectFile(false, "SubCategoryForSchemaValidationErrors", new BuildEventFileInfo(e), "InvalidSchemaFile", schemaFile, e.Message); } } }
/// <summary> /// Given a task name, this method retrieves the task class. If the task has been requested before, it will be found in /// the class cache; otherwise, <UsingTask> declarations will be used to search the appropriate assemblies. /// </summary> /// <param name="taskName"></param> /// <param name="taskProjectFile"></param> /// <param name="taskNode"></param> /// <param name="exactMatchRequired"></param> /// <param name="loggingServices"></param> /// <param name="buildEventContext"></param> /// <param name="taskClass"></param> /// <returns>true, if task is found</returns> public bool GetRegisteredTask ( string taskName, string taskProjectFile, XmlNode taskNode, bool exactMatchRequired, EngineLoggingServices loggingServices, BuildEventContext buildEventContext, out LoadedType taskClass ) { taskClass = null; // If there are no using tags in the project don't bother caching or looking for tasks if (registeredTasks == null) { return false; } Hashtable cachedTaskClasses = exactMatchRequired ? this.cachedTaskClassesWithExactMatch : this.cachedTaskClassesWithFuzzyMatch; if (cachedTaskClasses.Contains(taskName)) { // Caller has asked us before for this same task name, and for the same value of "bool exactMatchRequired". // Return whatever the previous result was, even if it was null. Why would the result be different than // it was before? NOTE: Hash tables CAN have "null" as their value, and this still returns "true" for Contains(...). taskClass = (LoadedType) cachedTaskClasses[taskName]; } else { Hashtable registeredTasksFound; // look for the given task name in the registry; if not found, gather all registered task names that partially // match the given name if (FindRegisteredTasks(taskName, exactMatchRequired, out registeredTasksFound)) { foreach (DictionaryEntry registeredTaskFound in registeredTasksFound) { string mostSpecificTaskName = (string)registeredTaskFound.Key; // if the given task name is longer than the registered task name if (taskName.Length > ((string)registeredTaskFound.Key).Length) { // we will use the longer name to help disambiguate between multiple matches mostSpecificTaskName = taskName; } if (GetTaskFromAssembly(mostSpecificTaskName, (ArrayList)registeredTaskFound.Value, taskProjectFile, taskNode, loggingServices, buildEventContext, out taskClass)) { // Whilst we are within the processing of the task, we haven't actually started executing it, so // our using task message needs to be in the context of the target. However any errors should be reported // at the point where the task appears in the project. BuildEventContext usingTaskContext = new BuildEventContext(buildEventContext.NodeId, buildEventContext.TargetId, buildEventContext.ProjectContextId, BuildEventContext.InvalidTaskId); loggingServices.LogComment(usingTaskContext, "TaskFound", taskName, taskClass.Assembly.ToString()); break; } } } // Cache the result, even if it is null. We should never again do the work we just did, for this task name. cachedTaskClasses[taskName] = taskClass; } return (taskClass != null); }