// // Main entry point for parser. // You pass in the expression you want to parse, and you get an // ExpressionTree out the back end. // internal GenericExpressionNode Parse(string expression, ParserOptions optionSettings, ElementLocation elementLocation) { // We currently have no support (and no scenarios) for disallowing property references // in Conditions. ErrorUtilities.VerifyThrow(0 != (optionSettings & ParserOptions.AllowProperties), "Properties should always be allowed."); _options = optionSettings; _elementLocation = elementLocation; _lexer = new Scanner(expression, _options); if (!_lexer.Advance()) { errorPosition = _lexer.GetErrorPosition(); ProjectErrorUtilities.VerifyThrowInvalidProject(false, elementLocation, _lexer.GetErrorResource(), expression, errorPosition, _lexer.UnexpectedlyFound); } GenericExpressionNode node = Expr(expression); if (!_lexer.IsNext(Token.TokenType.EndOfInput)) { errorPosition = _lexer.GetErrorPosition(); ProjectErrorUtilities.VerifyThrowInvalidProject(false, elementLocation, "UnexpectedTokenInCondition", expression, _lexer.IsNextString(), errorPosition); } return(node); }
/// <summary> /// Construct a target specification. /// </summary> /// <param name="targetName">The name of the target</param> /// <param name="referenceLocation">The location from which it was referred.</param> internal TargetSpecification(string targetName, ElementLocation referenceLocation) { ErrorUtilities.VerifyThrowArgumentLength(targetName, "targetName"); ErrorUtilities.VerifyThrowArgumentNull(referenceLocation, "referenceLocation"); this._targetName = targetName; this._referenceLocation = referenceLocation; }
/// <summary> /// Determines how many times the batchable object needs to be executed (each execution is termed a "batch"), and prepares /// buckets of items to pass to the object in each batch. /// </summary> /// <returns>List containing ItemBucket objects, each one representing an execution batch.</returns> internal static List <ItemBucket> PrepareBatchingBuckets ( List <string> batchableObjectParameters, Lookup lookup, ElementLocation elementLocation ) { return(PrepareBatchingBuckets(batchableObjectParameters, lookup, null, elementLocation)); }
/// <summary> /// Constructor /// </summary> public TaskFactoryLoggingHost(bool isRunningWithMultipleNodes, ElementLocation elementLocation, BuildLoggingContext loggingContext) { ErrorUtilities.VerifyThrowArgumentNull(loggingContext, "loggingContext"); ErrorUtilities.VerifyThrowInternalNull(elementLocation, "elementLocation"); _activeProxy = true; _isRunningWithMultipleNodes = isRunningWithMultipleNodes; _loggingContext = loggingContext; _elementLocation = elementLocation; }
/// <summary> /// Constructor /// </summary> /// <param name="host">The component host</param> /// <param name="requestEntry">The build request entry</param> /// <param name="taskLocation">The <see cref="ElementLocation"/> of the task.</param> /// <param name="targetBuilderCallback">An <see cref="ITargetBuilderCallback"/> to use to invoke targets and build projects.</param> public TaskHost(IBuildComponentHost host, BuildRequestEntry requestEntry, ElementLocation taskLocation, ITargetBuilderCallback targetBuilderCallback) { ErrorUtilities.VerifyThrowArgumentNull(host, "host"); ErrorUtilities.VerifyThrowArgumentNull(requestEntry, "requestEntry"); ErrorUtilities.VerifyThrowInternalNull(taskLocation, "taskLocation"); _host = host; _requestEntry = requestEntry; _taskLocation = taskLocation; _targetBuilderCallback = targetBuilderCallback; _continueOnError = false; _activeProxy = true; _callbackMonitor = new Object(); }
/// <summary> /// Gets the values of the specified metadata for the given item. /// The keys in the dictionary returned may be qualified and/or unqualified, exactly /// as they are found in the metadata reference. /// For example if %(x) is found, the key is "x", if %(z.x) is found, the key is "z.x". /// This dictionary in each bucket is used by Expander to expand exactly the same metadata references, so /// %(x) is expanded using the key "x", and %(z.x) is expanded using the key "z.x". /// </summary> /// <returns>the metadata values</returns> private static Dictionary <string, string> GetItemMetadataValues ( ProjectItemInstance item, Dictionary <string, MetadataReference> consumedMetadataReferences, ElementLocation elementLocation ) { var itemMetadataValues = new Dictionary <string, string>(consumedMetadataReferences.Count, MSBuildNameIgnoreCaseComparer.Default); foreach (KeyValuePair <string, MetadataReference> consumedMetadataReference in consumedMetadataReferences) { string metadataQualifiedName = consumedMetadataReference.Key; string metadataItemName = consumedMetadataReference.Value.ItemName; string metadataName = consumedMetadataReference.Value.MetadataName; if ( (metadataItemName != null) && (0 != String.Compare(item.ItemType, metadataItemName, StringComparison.OrdinalIgnoreCase)) ) { itemMetadataValues[metadataQualifiedName] = String.Empty; } else { try { // This returns String.Empty for both metadata that is undefined and metadata that has // an empty value; they are treated the same. itemMetadataValues[metadataQualifiedName] = ((IItem)item).GetMetadataValueEscaped(metadataName); } catch (InvalidOperationException e) { ProjectErrorUtilities.VerifyThrowInvalidProject(false, elementLocation, "CannotEvaluateItemMetadata", metadataName, e.Message); } } } return(itemMetadataValues); }
/// <summary> /// Indicates to the TaskHost that it is no longer needed. /// Called by TaskBuilder when the task using the EngineProxy is done. /// </summary> internal void MarkAsInactive() { VerifyActiveProxy(); _activeProxy = false; _loggingContext = null; _elementLocation = null; // Clear out the sponsor (who is responsible for keeping the EngineProxy remoting lease alive until the task is done) // this will be null if the engineproxy was never sent across an appdomain boundry. if (_sponsor != null) { ILease lease = (ILease)RemotingServices.GetLifetimeService(this); if (lease != null) { lease.Unregister(_sponsor); } _sponsor.Close(); _sponsor = null; } }
/// <summary> /// Partitions the items consumed by the batchable object into buckets, where each bucket contains a set of items that /// have the same value set on all item metadata consumed by the object. /// </summary> /// <remarks> /// PERF NOTE: Given n items and m batching metadata that produce l buckets, it is usually the case that n > l > m, /// because a batchable object typically uses one or two item metadata to control batching, and only has a handful of /// buckets. The number of buckets is typically only large if a batchable object is using single-item batching /// (where l == n). Any algorithm devised for bucketing therefore, should try to minimize n and l in its complexity /// equation. The algorithm below has a complexity of O(n*lg(l)*m/2) in its comparisons, and is effectively O(n) when /// l is small, and O(n*lg(n)) in the worst case as l -> n. However, note that the comparison complexity is not the /// same as the operational complexity for this algorithm. The operational complexity of this algorithm is actually /// O(n*m + n*lg(l)*m/2 + n*l/2 + n + l), which is effectively O(n^2) in the worst case. The additional complexity comes /// from the array and metadata operations that are performed. However, those operations are extremely cheap compared /// to the comparison operations, which dominate the time spent in this method. /// </remarks> /// <returns>List containing ItemBucket objects (can be empty), each one representing an execution batch.</returns> private static List <ItemBucket> BucketConsumedItems ( Lookup lookup, Dictionary <string, ICollection <ProjectItemInstance> > itemListsToBeBatched, Dictionary <string, MetadataReference> consumedMetadataReferences, ElementLocation elementLocation ) { ErrorUtilities.VerifyThrow(itemListsToBeBatched.Count > 0, "Need item types consumed by the batchable object."); ErrorUtilities.VerifyThrow(consumedMetadataReferences.Count > 0, "Need item metadata consumed by the batchable object."); var buckets = new List <ItemBucket>(); // Get and iterate through the list of item names that we're supposed to batch on. foreach (KeyValuePair <string, ICollection <ProjectItemInstance> > entry in itemListsToBeBatched) { string itemName = entry.Key; // Use the previously-fetched items, if possible ICollection <ProjectItemInstance> items = entry.Value ?? lookup.GetItems(itemName); if (items != null) { foreach (ProjectItemInstance item in items) { // Get this item's values for all the metadata consumed by the batchable object. Dictionary <string, string> itemMetadataValues = GetItemMetadataValues(item, consumedMetadataReferences, elementLocation); // put the metadata into a dummy bucket we can use for searching ItemBucket dummyBucket = ItemBucket.GetDummyBucketForComparisons(itemMetadataValues); // look through all previously created buckets to find a bucket whose items have the same values as // this item for all metadata consumed by the batchable object int matchingBucketIndex = buckets.BinarySearch(dummyBucket); ItemBucket matchingBucket = (matchingBucketIndex >= 0) ? buckets[matchingBucketIndex] : null; // If we didn't find a bucket that matches this item, create a new one, adding // this item to the bucket. if (null == matchingBucket) { matchingBucket = new ItemBucket(itemListsToBeBatched.Keys, itemMetadataValues, lookup, buckets.Count); // make sure to put the new bucket into the appropriate location // in the sorted list as indicated by the binary search // NOTE: observe the ~ operator (bitwise complement) in front of // the index -- see MSDN for more information on the return value // from the List.BinarySearch() method buckets.Insert(~matchingBucketIndex, matchingBucket); } // We already have a bucket for this type of item, so add this item to // the bucket. matchingBucket.AddItem(item); } } } // Put the buckets back in the order in which they were discovered, so that the first // item declared in the project file ends up in the first batch passed into the target/task. var orderedBuckets = new List <ItemBucket>(buckets.Count); for (int i = 0; i < buckets.Count; ++i) { orderedBuckets.Add(null); } foreach (ItemBucket bucket in buckets) { orderedBuckets[bucket.BucketSequenceNumber] = bucket; } return(orderedBuckets); }
/// <summary> /// Of all the item lists that are referenced in this batchable object, which ones should we /// batch on, and which ones should we just pass in wholesale to every invocation of the /// target/task? /// /// Rule #1. If the user has referenced any *qualified* item metadata such as %(EmbeddedResource.Culture), /// then that item list "EmbeddedResource" will definitely get batched. /// /// Rule #2. For all the unqualified item metadata such as %(Culture), we make sure that /// every single item in every single item list being passed into the task contains a value /// for that metadata. If not, it's an error. If so, we batch all of those item lists. /// /// All other item lists will not be batched, and instead will be passed in wholesale to all buckets. /// </summary> /// <returns>Dictionary containing the item names that should be batched. If the items match unqualified metadata, /// the entire list of items will be returned in the Value. Otherwise, the Value will be empty, indicating only the /// qualified item set (in the Key) should be batched. /// </returns> private static Dictionary <string, ICollection <ProjectItemInstance> > GetItemListsToBeBatched ( Dictionary <string, MetadataReference> consumedMetadataReferences, // Key is [string] potentially qualified metadata name // Value is [struct MetadataReference] HashSet <string> consumedItemReferenceNames, Lookup lookup, ElementLocation elementLocation ) { // The keys in this hashtable are the names of the items that we will batch on. // The values are always String.Empty (not used). var itemListsToBeBatched = new Dictionary <string, ICollection <ProjectItemInstance> >(MSBuildNameIgnoreCaseComparer.Default); // Loop through all the metadata references and find the ones that are qualified // with an item name. foreach (MetadataReference consumedMetadataReference in consumedMetadataReferences.Values) { if (consumedMetadataReference.ItemName != null) { // Rule #1. Qualified metadata reference. // For metadata references that are qualified with an item name // (e.g., %(EmbeddedResource.Culture) ), we add that item name to the list of // consumed item names, even if the item name wasn't otherwise referenced via // @(...) syntax, and even if every item in the list doesn't necessary contain // a value for this metadata. This is the special power that you get by qualifying // the metadata reference with an item name. itemListsToBeBatched[consumedMetadataReference.ItemName] = null; // Also add this qualified item to the consumed item references list, because // %(EmbeddedResource.Culture) effectively means that @(EmbeddedResource) is // being consumed, even though we may not see literally "@(EmbeddedResource)" // in the tag anywhere. Adding it to this list allows us (down below in this // method) to check that every item in this list has a value for each // unqualified metadata reference. consumedItemReferenceNames = consumedItemReferenceNames ?? new HashSet <string>(MSBuildNameIgnoreCaseComparer.Default); consumedItemReferenceNames.Add(consumedMetadataReference.ItemName); } } // Loop through all the metadata references and find the ones that are unqualified. foreach (MetadataReference consumedMetadataReference in consumedMetadataReferences.Values) { if (consumedMetadataReference.ItemName == null) { // Rule #2. Unqualified metadata reference. // For metadata references that are unqualified, every single consumed item // must contain a value for that metadata. If any item doesn't, it's an error // to use unqualified metadata. if (consumedItemReferenceNames != null) { foreach (string consumedItemName in consumedItemReferenceNames) { // Loop through all the items in the item list. ICollection <ProjectItemInstance> items = lookup.GetItems(consumedItemName); if (items != null) { // Loop through all the items in the BuildItemGroup. foreach (ProjectItemInstance item in items) { ProjectErrorUtilities.VerifyThrowInvalidProject( item.HasMetadata(consumedMetadataReference.MetadataName), elementLocation, "ItemDoesNotContainValueForUnqualifiedMetadata", item.EvaluatedInclude, consumedItemName, consumedMetadataReference.MetadataName); } } // This item list passes the test of having every single item containing // a value for this metadata. Therefore, add this item list to the batching list. // Also, to save doing lookup.GetItems again, put the items in the table as the value. itemListsToBeBatched[consumedItemName] = items; } } } } return(itemListsToBeBatched); }
/// <summary> /// Determines how many times the batchable object needs to be executed (each execution is termed a "batch"), and prepares /// buckets of items to pass to the object in each batch. /// </summary> /// <param name="elementLocation"></param> /// <param name="batchableObjectParameters"></param> /// <param name="lookup"></param> /// <param name="implicitBatchableItemType">Any item type that can be considered an implicit input to this batchable object. /// This is useful for items inside targets, where the item name is plainly an item type that's an "input" to the object.</param> /// <returns>List containing ItemBucket objects, each one representing an execution batch.</returns> internal static List <ItemBucket> PrepareBatchingBuckets ( List <string> batchableObjectParameters, Lookup lookup, string implicitBatchableItemType, ElementLocation elementLocation ) { if (batchableObjectParameters == null) { ErrorUtilities.ThrowInternalError("Need the parameters of the batchable object to determine if it can be batched."); } if (lookup == null) { ErrorUtilities.ThrowInternalError("Need to specify the lookup."); } ItemsAndMetadataPair pair = ExpressionShredder.GetReferencedItemNamesAndMetadata(batchableObjectParameters); // All the @(itemname) item list references in the tag, including transforms, etc. HashSet <string> consumedItemReferences = pair.Items; // All the %(itemname.metadataname) references in the tag (not counting those embedded // inside item transforms), and note that the itemname portion is optional. // The keys in the returned hash table are the qualified metadata names (e.g. "EmbeddedResource.Culture" // or just "Culture"). The values are MetadataReference structs, which simply split out the item // name (possibly null) and the actual metadata name. Dictionary <string, MetadataReference> consumedMetadataReferences = pair.Metadata; List <ItemBucket> buckets = null; if (consumedMetadataReferences != null && consumedMetadataReferences.Count > 0) { // Add any item types that we were explicitly told to assume. if (implicitBatchableItemType != null) { consumedItemReferences = consumedItemReferences ?? new HashSet <string>(MSBuildNameIgnoreCaseComparer.Default); consumedItemReferences.Add(implicitBatchableItemType); } // This method goes through all the item list references and figures out which ones // will be participating in batching, and which ones won't. We get back a hashtable // where the key is the item name that will be participating in batching. The values // are all String.Empty (not used). This method may return additional item names // that weren't represented in "consumedItemReferences"... this would happen if there // were qualified metadata references in the consumedMetadataReferences table, such as // %(EmbeddedResource.Culture). Dictionary <string, ICollection <ProjectItemInstance> > itemListsToBeBatched = GetItemListsToBeBatched(consumedMetadataReferences, consumedItemReferences, lookup, elementLocation); // At this point, if there were any metadata references in the tag, but no item // references to batch on, we've got a problem because we can't figure out which // item lists the user wants us to batch. if (itemListsToBeBatched.Count == 0) { foreach (string unqualifiedMetadataName in consumedMetadataReferences.Keys) { // Of course, since this throws an exception, there's no way we're ever going // to really loop here... it's just that the foreach is the only way I can // figure out how to get data out of the hashtable without knowing any of the // keys! ProjectErrorUtilities.VerifyThrowInvalidProject(false, elementLocation, "CannotReferenceItemMetadataWithoutItemName", unqualifiedMetadataName); } } else { // If the batchable object consumes item metadata as well as items to be batched, // we need to partition the items consumed by the object. buckets = BucketConsumedItems(lookup, itemListsToBeBatched, consumedMetadataReferences, elementLocation); } } // if the batchable object does not consume any item metadata or items, or if the item lists it consumes are all // empty, then the object does not need to be batched if ((buckets == null) || (buckets.Count == 0)) { // create a default bucket that references the project items and properties -- this way we always have a bucket buckets = new List <ItemBucket>(1); buckets.Add(new ItemBucket(null, null, lookup, buckets.Count)); } return(buckets); }
/// <summary> /// Pushes the list of targets specified onto the target stack in reverse order specified, so that /// they will be built in the order specified. /// </summary> /// <param name="targets">List of targets to build.</param> /// <param name="parentTargetEntry">The target which should be considered the parent of these targets.</param> /// <param name="baseLookup">The lookup to be used to build these targets.</param> /// <param name="addAsErrorTarget">True if this should be considered an error target.</param> /// <param name="stopProcessingOnCompletion">True if target stack processing should terminate when the last target in the list is processed.</param> /// <param name="buildReason">The reason the target is being built by the parent.</param> /// <returns>True if we actually pushed any targets, false otherwise.</returns> private async Task <bool> PushTargets(IList <TargetSpecification> targets, TargetEntry parentTargetEntry, Lookup baseLookup, bool addAsErrorTarget, bool stopProcessingOnCompletion, TargetBuiltReason buildReason) { List <TargetEntry> targetsToPush = new List <TargetEntry>(targets.Count); // Iterate the list in reverse order so that the first target in the list is the last pushed, and thus the first to be executed. for (int i = targets.Count - 1; i >= 0; i--) { TargetSpecification targetSpecification = targets[i]; if (buildReason == TargetBuiltReason.BeforeTargets || buildReason == TargetBuiltReason.AfterTargets) { // Don't build any Before or After targets for which we already have results. Unlike other targets, // we don't explicitly log a skipped-with-results message because it is not interesting. if (_buildResult.HasResultsForTarget(targetSpecification.TargetName)) { if (_buildResult[targetSpecification.TargetName].ResultCode != TargetResultCode.Skipped) { continue; } } } ElementLocation targetLocation = targetSpecification.ReferenceLocation; // See if this target is already building under a different build request. If so, we need to wait. int idOfAlreadyBuildingRequest = BuildRequest.InvalidGlobalRequestId; if (_requestEntry.RequestConfiguration.ActivelyBuildingTargets.TryGetValue(targetSpecification.TargetName, out idOfAlreadyBuildingRequest)) { if (idOfAlreadyBuildingRequest != _requestEntry.Request.GlobalRequestId) { // Another request elsewhere is building it. We need to wait. await _requestBuilderCallback.BlockOnTargetInProgress(idOfAlreadyBuildingRequest, targetSpecification.TargetName, null); // If we come out of here and the target is *still* active, it means the scheduler detected a circular dependency and told us to // continue so we could throw the exception. if (_requestEntry.RequestConfiguration.ActivelyBuildingTargets.ContainsKey(targetSpecification.TargetName)) { ProjectErrorUtilities.ThrowInvalidProject(targetLocation, "CircularDependency", targetSpecification.TargetName); } } else { if (buildReason == TargetBuiltReason.AfterTargets) { // If the target we are pushing is supposed to run after the current target and it is already set to run after us then skip adding it now. continue; } // We are already building this target on this request. That's a circular dependency. ProjectErrorUtilities.ThrowInvalidProject(targetLocation, "CircularDependency", targetSpecification.TargetName); } } else { // Does this target exist in our direct parent chain, if it is a before target (since these can cause circular dependency issues) if (buildReason == TargetBuiltReason.BeforeTargets || buildReason == TargetBuiltReason.DependsOn || buildReason == TargetBuiltReason.None) { TargetEntry currentParent = parentTargetEntry; while (currentParent != null) { if (String.Equals(currentParent.Name, targetSpecification.TargetName, StringComparison.OrdinalIgnoreCase)) { // We are already building this target on this request. That's a circular dependency. ProjectErrorUtilities.ThrowInvalidProject(targetLocation, "CircularDependency", targetSpecification.TargetName); } currentParent = currentParent.ParentEntry; } } else { // For an after target, if it is already ANYWHERE on the stack, we don't need to push it because it is already going to run // after whatever target is causing it to be pushed now. bool alreadyPushed = false; foreach (TargetEntry entry in _targetsToBuild) { if (String.Equals(entry.Name, targetSpecification.TargetName, StringComparison.OrdinalIgnoreCase)) { alreadyPushed = true; break; } } if (alreadyPushed) { continue; } } } // Add to the list of targets to push. We don't actually put it on the stack here because we could run into a circular dependency // during this loop, in which case the target stack would be out of whack. TargetEntry newEntry = new TargetEntry(_requestEntry, this as ITargetBuilderCallback, targetSpecification, baseLookup, parentTargetEntry, buildReason, _componentHost, stopProcessingOnCompletion); newEntry.ErrorTarget = addAsErrorTarget; targetsToPush.Add(newEntry); stopProcessingOnCompletion = false; // The first target on the stack (the last one to be run) always inherits the stopProcessing flag. } // Now push the targets since this operation cannot fail. foreach (TargetEntry targetToPush in targetsToPush) { _targetsToBuild.Push(targetToPush); } bool pushedTargets = (targetsToPush.Count > 0); return(pushedTargets); }
/// <summary> /// Runs all of the tasks for this target, batched as necessary. /// </summary> internal async Task ExecuteTarget(ITaskBuilder taskBuilder, BuildRequestEntry requestEntry, ProjectLoggingContext projectLoggingContext, CancellationToken cancellationToken) { #if MSBUILDENABLEVSPROFILING try { string beginTargetBuild = String.Format(CultureInfo.CurrentCulture, "Build Target {0} in Project {1} - Start", this.Name, projectFullPath); DataCollection.CommentMarkProfile(8800, beginTargetBuild); #endif try { VerifyState(_state, TargetEntryState.Execution); ErrorUtilities.VerifyThrow(!_isExecuting, "Target {0} is already executing", _target.Name); _cancellationToken = cancellationToken; _isExecuting = true; // Generate the batching buckets. Note that each bucket will get a lookup based on the baseLookup. This lookup will be in its // own scope, which we will collapse back down into the baseLookup at the bottom of the function. List <ItemBucket> buckets = BatchingEngine.PrepareBatchingBuckets(GetBatchableParametersForTarget(), _baseLookup, _target.Location); WorkUnitResult aggregateResult = new WorkUnitResult(); TargetLoggingContext targetLoggingContext = null; bool targetSuccess = false; int numberOfBuckets = buckets.Count; string projectFullPath = requestEntry.RequestConfiguration.ProjectFullPath; string parentTargetName = null; if (ParentEntry != null && ParentEntry.Target != null) { parentTargetName = ParentEntry.Target.Name; } for (int i = 0; i < numberOfBuckets; i++) { ItemBucket bucket = buckets[i]; // If one of the buckets failed, stop building. if (aggregateResult.ActionCode == WorkUnitActionCode.Stop) { break; } targetLoggingContext = projectLoggingContext.LogTargetBatchStarted(projectFullPath, _target, parentTargetName, _buildReason); WorkUnitResult bucketResult = null; targetSuccess = false; Lookup.Scope entryForInference = null; Lookup.Scope entryForExecution = null; try { // This isn't really dependency analysis. This is up-to-date checking. Based on this we will be able to determine if we should // run tasks in inference or execution mode (or both) or just skip them altogether. ItemDictionary <ProjectItemInstance> changedTargetInputs; ItemDictionary <ProjectItemInstance> upToDateTargetInputs; Lookup lookupForInference; Lookup lookupForExecution; // UNDONE: (Refactor) Refactor TargetUpToDateChecker to take a logging context, not a logging service. TargetUpToDateChecker dependencyAnalyzer = new TargetUpToDateChecker(requestEntry.RequestConfiguration.Project, _target, targetLoggingContext.LoggingService, targetLoggingContext.BuildEventContext); DependencyAnalysisResult dependencyResult = dependencyAnalyzer.PerformDependencyAnalysis(bucket, out changedTargetInputs, out upToDateTargetInputs); switch (dependencyResult) { // UNDONE: Need to enter/leave debugger scope properly for the <Target> element. case DependencyAnalysisResult.FullBuild: case DependencyAnalysisResult.IncrementalBuild: case DependencyAnalysisResult.SkipUpToDate: // Create the lookups used to hold the current set of properties and items lookupForInference = bucket.Lookup; lookupForExecution = bucket.Lookup.Clone(); // Push the lookup stack up one so that we are only modifying items and properties in that scope. entryForInference = lookupForInference.EnterScope("ExecuteTarget() Inference"); entryForExecution = lookupForExecution.EnterScope("ExecuteTarget() Execution"); // if we're doing an incremental build, we need to effectively run the task twice -- once // to infer the outputs for up-to-date input items, and once to actually execute the task; // as a result we need separate sets of item and property collections to track changes if (dependencyResult == DependencyAnalysisResult.IncrementalBuild) { // subset the relevant items to those that are up-to-date foreach (string itemType in upToDateTargetInputs.ItemTypes) { lookupForInference.PopulateWithItems(itemType, upToDateTargetInputs[itemType]); } // subset the relevant items to those that have changed foreach (string itemType in changedTargetInputs.ItemTypes) { lookupForExecution.PopulateWithItems(itemType, changedTargetInputs[itemType]); } } // We either have some work to do or at least we need to infer outputs from inputs. bucketResult = await ProcessBucket(taskBuilder, targetLoggingContext, GetTaskExecutionMode(dependencyResult), lookupForInference, lookupForExecution); // Now aggregate the result with the existing known results. There are four rules, assuming the target was not // skipped due to being up-to-date: // 1. If this bucket failed or was cancelled, the aggregate result is failure. // 2. If this bucket Succeeded and we have not previously failed, the aggregate result is a success. // 3. Otherwise, the bucket was skipped, which has no effect on the aggregate result. // 4. If the bucket's action code says to stop, then we stop, regardless of the success or failure state. if (dependencyResult != DependencyAnalysisResult.SkipUpToDate) { aggregateResult = aggregateResult.AggregateResult(bucketResult); } else { if (aggregateResult.ResultCode == WorkUnitResultCode.Skipped) { aggregateResult = aggregateResult.AggregateResult(new WorkUnitResult(WorkUnitResultCode.Success, WorkUnitActionCode.Continue, null)); } } // Pop the lookup scopes, causing them to collapse their values back down into the // bucket's lookup. // NOTE: this order is important because when we infer outputs, we are trying // to produce the same results as would be produced from a full build; as such // if we're doing both the infer and execute steps, we want the outputs from // the execute step to override the outputs of the infer step -- this models // the full build scenario more correctly than if the steps were reversed entryForInference.LeaveScope(); entryForInference = null; entryForExecution.LeaveScope(); entryForExecution = null; targetSuccess = (bucketResult != null) && (bucketResult.ResultCode == WorkUnitResultCode.Success); break; case DependencyAnalysisResult.SkipNoInputs: case DependencyAnalysisResult.SkipNoOutputs: // We have either no inputs or no outputs, so there is nothing to do. targetSuccess = true; break; } } catch (InvalidProjectFileException e) { // Make sure the Invalid Project error gets logged *before* TargetFinished. Otherwise, // the log is confusing. targetLoggingContext.LogInvalidProjectFileError(e); if (null != entryForInference) { entryForInference.LeaveScope(); } if (null != entryForExecution) { entryForExecution.LeaveScope(); } aggregateResult = aggregateResult.AggregateResult(new WorkUnitResult(WorkUnitResultCode.Failed, WorkUnitActionCode.Stop, null)); } finally { // Don't log the last target finished event until we can process the target outputs as we want to attach them to the // last target batch. if (targetLoggingContext != null && i < numberOfBuckets - 1) { targetLoggingContext.LogTargetBatchFinished(projectFullPath, targetSuccess, null); targetLoggingContext = null; } } } // Produce the final results. List <TaskItem> targetOutputItems = new List <TaskItem>(); try { // If any legacy CallTarget operations took place, integrate them back in to the main lookup now. LeaveLegacyCallTargetScopes(); // Publish the items for each bucket back into the baseLookup. Note that EnterScope() was actually called on each // bucket inside of the ItemBucket constructor, which is why you don't see it anywhere around here. foreach (ItemBucket bucket in buckets) { bucket.LeaveScope(); } string targetReturns = _target.Returns; ElementLocation targetReturnsLocation = _target.ReturnsLocation; // If there are no targets in the project file that use the "Returns" attribute, that means that we // revert to the legacy behavior in the case where Returns is not specified (null, rather // than the empty string, which indicates no returns). Legacy behavior is for all // of the target's Outputs to be returned. // On the other hand, if there is at least one target in the file that uses the Returns attribute, // then all targets in the file are run according to the new behaviour (return nothing unless otherwise // specified by the Returns attribute). if (targetReturns == null) { if (!_target.ParentProjectSupportsReturnsAttribute) { targetReturns = _target.Outputs; targetReturnsLocation = _target.OutputsLocation; } } if (!String.IsNullOrEmpty(targetReturns)) { // Determine if we should keep duplicates. bool keepDupes = ConditionEvaluator.EvaluateCondition ( _target.KeepDuplicateOutputs, ParserOptions.AllowPropertiesAndItemLists, _expander, ExpanderOptions.ExpandPropertiesAndItems, requestEntry.ProjectRootDirectory, _target.KeepDuplicateOutputsLocation, projectLoggingContext.LoggingService, projectLoggingContext.BuildEventContext, FileSystems.Default); // NOTE: we need to gather the outputs in batches, because the output specification may reference item metadata // Also, we are using the baseLookup, which has possibly had changes made to it since the project started. Because of this, the // set of outputs calculated here may differ from those which would have been calculated at the beginning of the target. It is // assumed the user intended this. List <ItemBucket> batchingBuckets = BatchingEngine.PrepareBatchingBuckets(GetBatchableParametersForTarget(), _baseLookup, _target.Location); if (keepDupes) { foreach (ItemBucket bucket in batchingBuckets) { targetOutputItems.AddRange(bucket.Expander.ExpandIntoTaskItemsLeaveEscaped(targetReturns, ExpanderOptions.ExpandAll, targetReturnsLocation)); } } else { HashSet <TaskItem> addedItems = new HashSet <TaskItem>(); foreach (ItemBucket bucket in batchingBuckets) { IList <TaskItem> itemsToAdd = bucket.Expander.ExpandIntoTaskItemsLeaveEscaped(targetReturns, ExpanderOptions.ExpandAll, targetReturnsLocation); foreach (TaskItem item in itemsToAdd) { if (!addedItems.Contains(item)) { targetOutputItems.Add(item); addedItems.Add(item); } } } } } } finally { if (targetLoggingContext != null) { // log the last target finished since we now have the target outputs. targetLoggingContext.LogTargetBatchFinished(projectFullPath, targetSuccess, targetOutputItems != null && targetOutputItems.Count > 0 ? targetOutputItems : null); } } _targetResult = new TargetResult(targetOutputItems.ToArray(), aggregateResult); if (aggregateResult.ResultCode == WorkUnitResultCode.Failed && aggregateResult.ActionCode == WorkUnitActionCode.Stop) { _state = TargetEntryState.ErrorExecution; } else { _state = TargetEntryState.Completed; } } finally { _isExecuting = false; } #if MSBUILDENABLEVSPROFILING } finally { string endTargetBuild = String.Format(CultureInfo.CurrentCulture, "Build Target {0} in Project {1} - End", this.Name, projectFullPath); DataCollection.CommentMarkProfile(8801, endTargetBuild); } #endif }
/// <summary> /// Is the given task name able to be created by the task factory. In the case of an assembly task factory /// this question is answered by checking the assembly wrapped by the task factory to see if it exists. /// </summary> internal bool TaskNameCreatableByFactory(string taskName, IDictionary <string, string> taskIdentityParameters, string taskProjectFile, TargetLoggingContext targetLoggingContext, ElementLocation elementLocation) { if (!TaskIdentityParametersMatchFactory(_factoryIdentityParameters, taskIdentityParameters)) { return(false); } // Parameters match, so now we check to see if the task exists. LoadedType taskClass = null; try { ErrorUtilities.VerifyThrowArgumentLength(taskName, "TaskName"); taskClass = _typeLoader.ReflectionOnlyLoad(taskName, _loadedType.Assembly); if (taskClass != null) { return(true); } else { return(false); } } catch (TargetInvocationException e) { // Exception thrown by the called code itself // Log the stack, so the task vendor can fix their code ProjectErrorUtilities.ThrowInvalidProject(elementLocation, "TaskLoadFailure", taskName, _loadedType.Assembly.AssemblyLocation, Environment.NewLine + e.InnerException.ToString()); } catch (ReflectionTypeLoadException e) { // ReflectionTypeLoadException.LoaderExceptions may contain nulls foreach (Exception exception in e.LoaderExceptions) { if (exception != null) { targetLoggingContext.LogError(new BuildEventFileInfo(taskProjectFile), "TaskLoadFailure", taskName, _loadedType.Assembly.AssemblyLocation, exception.Message); } } ProjectErrorUtilities.ThrowInvalidProject(elementLocation, "TaskLoadFailure", taskName, _loadedType.Assembly.AssemblyLocation, e.Message); } catch (ArgumentNullException e) { // taskName may be null ProjectErrorUtilities.ThrowInvalidProject(elementLocation, "TaskLoadFailure", taskName, _loadedType.Assembly.AssemblyLocation, e.Message); } catch (Exception e) // Catching Exception, but rethrowing unless it's a well-known exception. { if (ExceptionHandling.NotExpectedReflectionException(e)) { throw; } ProjectErrorUtilities.ThrowInvalidProject(elementLocation, "TaskLoadFailure", taskName, _loadedType.Assembly.AssemblyLocation, e.Message); } return(false); }
/// <summary> /// Create an instance of the wrapped ITask for a batch run of the task. /// </summary> internal ITask CreateTaskInstance(ElementLocation taskLocation, TaskLoggingContext taskLoggingContext, IBuildComponentHost buildComponentHost, IDictionary <string, string> taskIdentityParameters, #if FEATURE_APPDOMAIN AppDomainSetup appDomainSetup, #endif bool isOutOfProc) { bool useTaskFactory = false; IDictionary <string, string> mergedParameters = null; _taskLoggingContext = taskLoggingContext; // Optimization for the common (vanilla AssemblyTaskFactory) case -- only calculate // the task factory parameters if we have any to calculate; otherwise even if we // still launch the task factory, it will be with parameters corresponding to the // current process. if ((_factoryIdentityParameters != null && _factoryIdentityParameters.Count > 0) || (taskIdentityParameters != null && taskIdentityParameters.Count > 0)) { VerifyThrowIdentityParametersValid(taskIdentityParameters, taskLocation, _taskName, "MSBuildRuntime", "MSBuildArchitecture"); mergedParameters = MergeTaskFactoryParameterSets(_factoryIdentityParameters, taskIdentityParameters); useTaskFactory = !NativeMethodsShared.IsMono && (_taskHostFactoryExplicitlyRequested || !TaskHostParametersMatchCurrentProcess(mergedParameters)); } else { // if we don't have any task host parameters specified on either the using task or the // task invocation, then we will run in-proc UNLESS "TaskHostFactory" is explicitly specified // as the task factory. useTaskFactory = _taskHostFactoryExplicitlyRequested; } if (useTaskFactory) { ErrorUtilities.VerifyThrowInternalNull(buildComponentHost, "buildComponentHost"); mergedParameters = mergedParameters ?? new Dictionary <string, string>(StringComparer.OrdinalIgnoreCase); string runtime = null; string architecture = null; if (!mergedParameters.TryGetValue(XMakeAttributes.runtime, out runtime)) { mergedParameters[XMakeAttributes.runtime] = XMakeAttributes.MSBuildRuntimeValues.clr4; } if (!mergedParameters.TryGetValue(XMakeAttributes.architecture, out architecture)) { mergedParameters[XMakeAttributes.architecture] = XMakeAttributes.GetCurrentMSBuildArchitecture(); } TaskHostTask task = new TaskHostTask(taskLocation, taskLoggingContext, buildComponentHost, mergedParameters, _loadedType #if FEATURE_APPDOMAIN , appDomainSetup #endif ); return(task); } else { #if FEATURE_APPDOMAIN AppDomain taskAppDomain = null; #endif ITask taskInstance = TaskLoader.CreateTask(_loadedType, _taskName, taskLocation.File, taskLocation.Line, taskLocation.Column, new TaskLoader.LogError(ErrorLoggingDelegate) #if FEATURE_APPDOMAIN , appDomainSetup #endif , isOutOfProc #if FEATURE_APPDOMAIN , out taskAppDomain #endif ); #if FEATURE_APPDOMAIN if (taskAppDomain != null) { _tasksAndAppDomains[taskInstance] = taskAppDomain; } #endif return(taskInstance); } }
/// <summary> /// Initialize the factory from the task registry /// </summary> internal LoadedType InitializeFactory ( AssemblyLoadInfo loadInfo, string taskName, IDictionary <string, TaskPropertyInfo> taskParameters, string taskElementContents, IDictionary <string, string> taskFactoryIdentityParameters, bool taskHostFactoryExplicitlyRequested, TargetLoggingContext targetLoggingContext, ElementLocation elementLocation, string taskProjectFile ) { ErrorUtilities.VerifyThrowArgumentNull(loadInfo, "loadInfo"); VerifyThrowIdentityParametersValid(taskFactoryIdentityParameters, elementLocation, taskName, "Runtime", "Architecture"); if (taskFactoryIdentityParameters != null) { _factoryIdentityParameters = new Dictionary <string, string>(taskFactoryIdentityParameters, StringComparer.OrdinalIgnoreCase); } _taskHostFactoryExplicitlyRequested = taskHostFactoryExplicitlyRequested; try { ErrorUtilities.VerifyThrowArgumentLength(taskName, "taskName"); _taskName = taskName; _loadedType = _typeLoader.Load(taskName, loadInfo); ProjectErrorUtilities.VerifyThrowInvalidProject(_loadedType != null, elementLocation, "TaskLoadFailure", taskName, loadInfo.AssemblyLocation, String.Empty); } catch (TargetInvocationException e) { // Exception thrown by the called code itself // Log the stack, so the task vendor can fix their code ProjectErrorUtilities.VerifyThrowInvalidProject(false, elementLocation, "TaskLoadFailure", taskName, loadInfo.AssemblyLocation, Environment.NewLine + e.InnerException.ToString()); } catch (ReflectionTypeLoadException e) { // ReflectionTypeLoadException.LoaderExceptions may contain nulls foreach (Exception exception in e.LoaderExceptions) { if (exception != null) { targetLoggingContext.LogError(new BuildEventFileInfo(taskProjectFile), "TaskLoadFailure", taskName, loadInfo.AssemblyLocation, exception.Message); } } ProjectErrorUtilities.VerifyThrowInvalidProject(false, elementLocation, "TaskLoadFailure", taskName, loadInfo.AssemblyLocation, e.Message); } catch (ArgumentNullException e) { // taskName may be null ProjectErrorUtilities.VerifyThrowInvalidProject(false, elementLocation, "TaskLoadFailure", taskName, loadInfo.AssemblyLocation, e.Message); } catch (Exception e) // Catching Exception, but rethrowing unless it's a well-known exception. { if (ExceptionHandling.NotExpectedReflectionException(e)) { throw; } ProjectErrorUtilities.VerifyThrowInvalidProject(false, elementLocation, "TaskLoadFailure", taskName, loadInfo.AssemblyLocation, e.Message); } return(_loadedType); }
/// <summary> /// Returns a list of all items in the provided item group whose itemspecs match the specification, after it is split and any wildcards are expanded. /// If no items match, returns null. /// </summary> /// <param name="items">The items to match</param> /// <param name="specification">The specification to match against the items.</param> /// <param name="specificationLocation">The specification to match against the provided items</param> /// <param name="expander">The expander to use</param> /// <returns>A list of matching items</returns> private List <ProjectItemInstance> FindItemsMatchingSpecification ( ICollection <ProjectItemInstance> items, string specification, ElementLocation specificationLocation, Expander <ProjectPropertyInstance, ProjectItemInstance> expander ) { if (items.Count == 0 || specification.Length == 0) { return(null); } // This is a hashtable whose key is the filename for the individual items // in the Exclude list, after wildcard expansion. HashSet <string> specificationsToFind = new HashSet <string>(StringComparer.OrdinalIgnoreCase); // Split by semicolons var specificationPieces = expander.ExpandIntoStringListLeaveEscaped(specification, ExpanderOptions.ExpandAll, specificationLocation); foreach (string piece in specificationPieces) { // Take each individual path or file expression, and expand any // wildcards. Then loop through each file returned, and add it // to our hashtable. // Don't unescape wildcards just yet - if there were any escaped, the caller wants to treat them // as literals. Everything else is safe to unescape at this point, since we're only matching // against the file system. string[] fileList = _engineFileUtilities.GetFileListEscaped(Project.Directory, piece); foreach (string file in fileList) { // Now unescape everything, because this is the end of the road for this filename. // We're just going to compare it to the unescaped include path to filter out the // file excludes. specificationsToFind.Add(EscapingUtilities.UnescapeAll(file)); } } if (specificationsToFind.Count == 0) { return(null); } // Now loop through our list and filter out any that match a // filename in the remove list. List <ProjectItemInstance> itemsRemoved = new List <ProjectItemInstance>(); foreach (ProjectItemInstance item in items) { // Even if the case for the excluded files is different, they // will still get excluded, as expected. However, if the excluded path // references the same file in a different way, such as by relative // path instead of absolute path, we will not realize that they refer // to the same file, and thus we will not exclude it. if (specificationsToFind.Contains(item.EvaluatedInclude)) { itemsRemoved.Add(item); } } return(itemsRemoved); }
/// <summary> /// Empty impl /// </summary> Task <ITargetResult[]> ITargetBuilderCallback.LegacyCallTarget(string[] targets, bool continueOnError, ElementLocation referenceLocation) { throw new NotImplementedException(); }
/// <summary> /// Invokes the specified targets using Dev9 behavior. /// </summary> /// <param name="targets">The targets to build.</param> /// <param name="continueOnError">True to continue building the remaining targets if one fails.</param> /// <param name="taskLocation">The <see cref="ElementLocation"/> of the task.</param> /// <returns>The results for each target.</returns> /// <remarks> /// Dev9 behavior refers to the following: /// 1. The changes made during the calling target up to this point are NOT visible to this target. /// 2. The changes made by this target are NOT visible to the calling target. /// 3. Changes made by the calling target OVERRIDE changes made by this target. /// </remarks> async Task <ITargetResult[]> ITargetBuilderCallback.LegacyCallTarget(string[] targets, bool continueOnError, ElementLocation taskLocation) { List <TargetSpecification> targetToPush = new List <TargetSpecification>(); ITargetResult[] results = new TargetResult[targets.Length]; bool originalLegacyCallTargetContinueOnError = _legacyCallTargetContinueOnError; _legacyCallTargetContinueOnError = _legacyCallTargetContinueOnError || continueOnError; // Our lookup is the one used at the beginning of the calling target. Lookup callTargetLookup = _baseLookup; // We now record this lookup in the calling target's entry so that it may // leave the scope just before it commits its own changes to the base lookup. TargetEntry currentTargetEntry = _targetsToBuild.Peek(); currentTargetEntry.EnterLegacyCallTargetScope(callTargetLookup); ITaskBuilder taskBuilder = _componentHost.GetComponent(BuildComponentType.TaskBuilder) as ITaskBuilder; try { // Flag set to true if one of the targets we call fails. bool errorResult = false; // Now walk through the list of targets, invoking each one. for (int i = 0; i < targets.Length; i++) { if (_cancellationToken.IsCancellationRequested || errorResult) { results[i] = new TargetResult(Array.Empty <TaskItem>(), new WorkUnitResult(WorkUnitResultCode.Skipped, WorkUnitActionCode.Continue, null)); } else { targetToPush.Clear(); targetToPush.Add(new TargetSpecification(targets[i], taskLocation)); // We push the targets one at a time to emulate the original CallTarget behavior. bool pushed = await PushTargets(targetToPush, currentTargetEntry, callTargetLookup, false, true, TargetBuiltReason.None); ErrorUtilities.VerifyThrow(pushed, "Failed to push any targets onto the stack. Target: {0} Current Target: {1}", targets[i], currentTargetEntry.Target.Name); await ProcessTargetStack(taskBuilder); if (!_cancellationToken.IsCancellationRequested) { results[i] = _buildResult[targets[i]]; if (results[i].ResultCode == TargetResultCode.Failure) { errorResult = true; } } else { results[i] = new TargetResult(Array.Empty <TaskItem>(), new WorkUnitResult(WorkUnitResultCode.Skipped, WorkUnitActionCode.Continue, null)); } } } } finally { // Restore the state of the TargetBuilder to that it was prior to the CallTarget call. // Any targets we have pushed on at this point we need to get rid of since we aren't going to process them. // If there were normal task errors, standard error handling semantics would have taken care of them. // If there was an exception, such as a circular dependency error, items may still be on the stack so we must clear them. while (!Object.ReferenceEquals(_targetsToBuild.Peek(), currentTargetEntry)) { _targetsToBuild.Pop(); } _legacyCallTargetContinueOnError = originalLegacyCallTargetContinueOnError; ((IBuildComponent)taskBuilder).ShutdownComponent(); } return(results); }