/// <summary> /// Builds this target if it has not already been built as part of its parent project. Before we actually execute the /// tasks for this target, though, we first call on all the dependent targets to build themselves. /// This function may throw InvalidProjectFileException /// </summary> internal void Build ( ProjectBuildState buildContext ) { // Depending on the build state, we may do different things. switch (buildState) { case BuildState.InProgress: // In single proc mode if the build state was already "in progress" // and somebody just told us to build ourselves, it means that there is // a loop (circular dependency) in the target dependency graph. In multi // proc mode we need to analyze the dependency graph before we can // tell if there a circular dependency or if two independent chains // of targets happen to need the result of this target. if (parentEngine.Router.SingleThreadedMode || buildContext.ContainsCycle(this.Name)) { ProjectErrorUtilities.VerifyThrowInvalidProject(false, TargetElement, "CircularDependency", targetName); } else { buildContext.CurrentBuildContextState = ProjectBuildState.BuildContextState.WaitingForTarget; this.executionState.AddWaitingBuildContext(buildContext); } break; case BuildState.CompletedSuccessfully: case BuildState.CompletedUnsuccessfully: // If this target has already been built as part of this project, // we're not going to build it again. Just return the result // from when it was built previously. Note: This condition // could really only ever hold true if the user specifically // requested us to build multiple targets and there existed // a direct or indirect dependency relationship between two or // more of those top-level targets. // Note: we aren't really entering the target in question here, so don't use the target // event context. Using the target ID for skipped messages would force us to // cache the individual target IDs for unloaded projects and it's not really worth the trouble. // Just use the parent event context. parentEngine.LoggingServices.LogComment(buildContext.ProjectBuildEventContext, ((buildState == BuildState.CompletedSuccessfully) ? "TargetAlreadyCompleteSuccess" : "TargetAlreadyCompleteFailure"), this.targetName); // Only contexts which are generated from an MSBuild task could need // the outputs of this target, such contexts have a non-null evaluation // request if ((buildState == BuildState.CompletedSuccessfully) && (buildContext.BuildRequest.OutputsByTarget != null && buildContext.NameOfBlockingTarget == null)) { error.VerifyThrow( String.Compare(EscapingUtilities.UnescapeAll(buildContext.NameOfTargetInProgress), this.Name, StringComparison.OrdinalIgnoreCase) == 0, "The name of the target in progress is inconsistent with the target being built"); error.VerifyThrow(targetOutputItems != null, "If the target built successfully, we must have its outputs."); buildContext.BuildRequest.OutputsByTarget[Name] = targetOutputItems.ToArray(); } if (buildContext.NameOfBlockingTarget == null) { buildContext.BuildRequest.ResultByTarget[Name] = buildState; } break; case BuildState.NotStarted: case BuildState.Skipped: { // Always have to create a new context in build as other projects or targets may try and build this target BuildEventContext targetBuildEventContext = new BuildEventContext ( buildContext.ProjectBuildEventContext.NodeId, this.id, buildContext.ProjectBuildEventContext.ProjectContextId, buildContext.ProjectBuildEventContext.TaskId ); Expander expander = new Expander(this.parentProject.evaluatedProperties, this.parentProject.evaluatedItemsByName); // We first make sure no batching was attempted with the target's condition. if (!conditionCheckedForInvalidMetadataReferences) { if (ExpressionShredder.ContainsMetadataExpressionOutsideTransform(this.Condition)) { ProjectErrorUtilities.ThrowInvalidProject(this.conditionAttribute, "TargetConditionHasInvalidMetadataReference", targetName, this.Condition); } conditionCheckedForInvalidMetadataReferences = true; } // If condition is false (based on propertyBag), set this target's state to // "Skipped" since we won't actually build it. if (!Utilities.EvaluateCondition(this.Condition, this.conditionAttribute, expander, null, ParserOptions.AllowProperties | ParserOptions.AllowItemLists, parentEngine.LoggingServices, targetBuildEventContext)) { buildState = BuildState.Skipped; if (buildContext.NameOfBlockingTarget == null) { buildContext.BuildRequest.ResultByTarget[Name] = buildState; } if (!parentEngine.LoggingServices.OnlyLogCriticalEvents) { // Expand the expression for the Log. string expanded = expander.ExpandAllIntoString(this.Condition, this.conditionAttribute); // By design: Not building dependencies. This is what NAnt does too. parentEngine.LoggingServices.LogComment(targetBuildEventContext, "TargetSkippedFalseCondition", this.targetName, this.Condition, expanded); } } else { // This target has not been built yet. So build it! // Change our state to "in progress". TargetParameters will need to be re-calculated if Inputs and Outputs attribute has changed. buildState = BuildState.InProgress; List <string> batchableTargetParameters = GetBatchableTargetParameters(); executionState = new TargetExecutionWrapper(this, taskElementList, batchableTargetParameters, targetElement, expander, targetBuildEventContext); ContinueBuild(buildContext, null); } } break; default: error.VerifyThrow(false, "Build state {0} not handled in Target.Build method", buildState); break; } }
/// <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="parentNode"></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>ArrayList containing ItemBucket objects, each one representing an execution batch.</returns> internal static ArrayList PrepareBatchingBuckets ( XmlNode parentNode, List <string> batchableObjectParameters, Lookup lookup, string implicitBatchableItemType ) { ErrorUtilities.VerifyThrow(parentNode != null, "Need the XML node that represents the batchable object."); ErrorUtilities.VerifyThrow(batchableObjectParameters != null, "Need the parameters of the batchable object to determine if it can be batched."); ErrorUtilities.VerifyThrow(lookup != null, "Need to specify the lookup."); ItemsAndMetadataPair pair = ExpressionShredder.GetReferencedItemNamesAndMetadata(batchableObjectParameters); // All the @(itemname) item list references in the tag, including transforms, etc. // The keys in the hashtable are the item names, and the values are all String.Empty (not used). Hashtable 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; ArrayList buckets = null; if (consumedMetadataReferences != null && consumedMetadataReferences.Count > 0) { // Add any item types that we were explicitly told to assume. if (implicitBatchableItemType != null) { consumedItemReferences = Utilities.CreateTableIfNecessary(consumedItemReferences); consumedItemReferences[implicitBatchableItemType] = String.Empty; } // 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). Hashtable itemListsToBeBatched = GetItemListsToBeBatched(parentNode, consumedMetadataReferences, consumedItemReferences, lookup); // 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, parentNode, "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(parentNode, lookup, itemListsToBeBatched, consumedMetadataReferences); } } // 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 ArrayList(1); buckets.Add(new ItemBucket(null, null, lookup, buckets.Count)); } return(buckets); }