/// <summary> /// Validates that a task's outputs are sane. If so, predicts output directories. /// </summary> /// <param name="inputs">The inputs specified in SourceFiles on a copy task.</param> /// <param name="outputs"> /// The outputs specified in the DestinationFolder or DestinationFiles attribute on a copy task. /// </param> /// <param name="copyTaskSpecifiesDestinationFolder">True if the user has specified DestinationFolder.</param> /// <param name="predictionReporter">A reporter to report predictions to.</param> private static void ProcessOutputs( FileExpressionList inputs, FileExpressionList outputs, bool copyTaskSpecifiesDestinationFolder, ProjectPredictionReporter predictionReporter) { for (int i = 0; i < inputs.DedupedFiles.Count; i++) { string predictedOutputDirectory; // If the user specified a destination folder, they could have specified an expression that evaluates to // either exactly one or N folders. We need to handle each case. if (copyTaskSpecifiesDestinationFolder) { if (outputs.DedupedFiles.Count == 0) { // Output files couldn't be parsed, bail out. break; } // If output directories isn't 1 or N, bail out. if (inputs.DedupedFiles.Count != outputs.DedupedFiles.Count && outputs.DedupedFiles.Count > 1) { break; } predictedOutputDirectory = outputs.DedupedFiles.Count == 1 ? outputs.DedupedFiles[0] : outputs.DedupedFiles[i]; } else { if (i >= outputs.DedupedFiles.Count) { break; } // The output list is a set of files. Predict their directories. predictedOutputDirectory = Path.GetDirectoryName(outputs.DedupedFiles[i]); } predictionReporter.ReportOutputDirectory(predictedOutputDirectory); } }
/// <summary> /// Parses the input and output files for copy tasks of given target. /// </summary> private static void ParseCopyTask( ProjectTargetInstance target, ProjectInstance projectInstance, ProjectPredictionReporter predictionReporter) { // Get all Copy tasks from targets. List <ProjectTaskInstance> tasks = target.Tasks .Where(task => string.Equals(task.Name, CopyTaskName, StringComparison.Ordinal)) .ToList(); if (tasks.Any() && projectInstance.EvaluateConditionCarefully(target.Condition)) { foreach (ProjectTaskInstance task in tasks) { if (projectInstance.EvaluateConditionCarefully(task.Condition)) { var inputs = new FileExpressionList( task.Parameters[CopyTaskSourceFiles], projectInstance, task); if (inputs.NumExpressions == 0) { continue; } foreach (var file in inputs.DedupedFiles) { predictionReporter.ReportInputFile(file); } bool hasDestinationFolder = task.Parameters.TryGetValue( CopyTaskDestinationFolder, out string destinationFolder); bool hasDestinationFiles = task.Parameters.TryGetValue( CopyTaskDestinationFiles, out string destinationFiles); if (hasDestinationFiles || hasDestinationFolder) { // Having both is an MSBuild violation, which it will complain about. if (hasDestinationFolder && hasDestinationFiles) { continue; } string destination = destinationFolder ?? destinationFiles; var outputs = new FileExpressionList(destination, projectInstance, task); // When using batch tokens, the user should specify exactly one total token, and it must appear in both the input and output. // Doing otherwise should be a BuildCop error. If not using batch tokens, then any number of other tokens is fine. if ((outputs.NumBatchExpressions == 1 && outputs.NumExpressions == 1 && inputs.NumBatchExpressions == 1 && inputs.NumExpressions == 1) || (outputs.NumBatchExpressions == 0 && inputs.NumBatchExpressions == 0)) { ProcessOutputs(inputs, outputs, hasDestinationFolder, predictionReporter); } else { // Ignore case we cannot handle. } } else { // Ignore malformed case. } } } } }