Replace BuildPrediction code with the Microsoft.Build.Prediction package (#411)

This commit is contained in:
David Federman 2019-06-05 14:16:36 -07:00 коммит произвёл GitHub
Родитель 9c845b4030
Коммит 77b685a956
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
70 изменённых файлов: 41 добавлений и 3344 удалений

Просмотреть файл

@ -1,43 +0,0 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
using System.Collections.Generic;
namespace Microsoft.Build.Prediction
{
/// <summary>
/// Data class for specifying a predicted build input, either a file or a directory.
/// </summary>
public class BuildInput : BuildItem
{
internal static readonly IEqualityComparer<BuildInput> ComparerInstance = new BuildInputComparer();
/// <summary>Initializes a new instance of the <see cref="BuildInput"/> class.</summary>
/// <param name="path">
/// Provides a rooted path to a predicted build input.
/// </param>
/// <param name="isDirectory">When true, the predicted path is a directory instead of a file.</param>
public BuildInput(string path, bool isDirectory)
: base(path)
{
IsDirectory = isDirectory;
}
internal BuildInput(string path, bool isDirectory, params string[] predictedBys)
: base(path, predictedBys)
{
IsDirectory = isDirectory;
}
/// <summary>
/// Gets a value indicating whether the predicted path is a directory instead of a file.
/// </summary>
public bool IsDirectory { get; }
/// <inheritdoc/>
public override string ToString()
{
return $"BuildInput: {Path} IsDir={IsDirectory} PredictedBy={string.Join(",", PredictedBy)}";
}
}
}

Просмотреть файл

@ -1,40 +0,0 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
using System.Collections.Generic;
using System.Linq;
namespace Microsoft.Build.Prediction
{
/// <summary>
/// Comparer class for <see cref="BuildInput"/>.
/// </summary>
internal sealed class BuildInputComparer : IEqualityComparer<BuildInput>
{
public bool Equals(BuildInput x, BuildInput y)
{
if (ReferenceEquals(x, y))
{
return true;
}
if (x is null || y is null)
{
return false;
}
return x.IsDirectory == y.IsDirectory &&
x.PredictedBy.Count == y.PredictedBy.Count &&
PathComparer.Instance.Equals(x.Path, y.Path) &&
x.PredictedBy.All(p => y.PredictedBy.Contains(p));
}
public int GetHashCode(BuildInput obj)
{
return (obj.IsDirectory ? 0x444444 : 0x888888) ^
PathComparer.Instance.GetHashCode(obj.Path) ^
(obj.PredictedBy.Count == 0 ? 0xAAAAAA : obj.PredictedBy.Count * 0xBB) ^
obj.PredictedBy.Aggregate(0, (current, s) => current ^ s.GetHashCode());
}
}
}

Просмотреть файл

@ -1,59 +0,0 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
using System;
using System.Collections.Generic;
namespace Microsoft.Build.Prediction
{
/// <summary>
/// Base class for <see cref="BuildInput"/> and <see cref="BuildOutputDirectory"/>.
/// </summary>
public abstract class BuildItem
{
private readonly HashSet<string> _predictedBy = new HashSet<string>(StringComparer.Ordinal);
// For unit testing
internal BuildItem(string path, params string[] predictedBys)
: this(path)
{
AddPredictedBy(predictedBys);
}
/// <summary>Initializes a new instance of the <see cref="BuildItem"/> class.</summary>
/// <param name="path">
/// Provides a rooted path to a predicted build input.
/// </param>
protected BuildItem(string path)
{
Path = path.ThrowIfNullOrEmpty(nameof(path));
}
/// <summary>
/// Gets a relative or (on Windows) rooted path to a predicted build item.
/// </summary>
public string Path { get; }
/// <summary>
/// Gets the class name of each contributor to this prediction, for debugging purposes.
/// These values are set in internally by <see cref="ProjectStaticPredictionExecutor"/>.
/// </summary>
public IReadOnlyCollection<string> PredictedBy => _predictedBy;
/// <summary>
/// Used for combining BuildInputs.
/// </summary>
internal void AddPredictedBy(IEnumerable<string> predictedBys)
{
foreach (string p in predictedBys)
{
_predictedBy.Add(p);
}
}
internal void AddPredictedBy(string predictedBy)
{
_predictedBy.Add(predictedBy);
}
}
}

Просмотреть файл

@ -1,33 +0,0 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
using System.Collections.Generic;
namespace Microsoft.Build.Prediction
{
/// <summary>
/// Data class for specifying a predicted output directory from an MSBuild <see cref="Microsoft.Build.Evaluation.Project"/>.
/// </summary>
public class BuildOutputDirectory : BuildItem
{
internal static readonly IEqualityComparer<BuildOutputDirectory> ComparerInstance = new BuildOutputDirectoryComparer();
/// <summary>Initializes a new instance of the <see cref="BuildOutputDirectory"/> class.</summary>
/// <param name="path">Provides a rooted path to the output directory.</param>
public BuildOutputDirectory(string path)
: base(path)
{
}
internal BuildOutputDirectory(string path, params string[] predictedBys)
: base(path, predictedBys)
{
}
/// <inheritdoc/>
public override string ToString()
{
return $"BuildOutputDirectory: {Path} PredictedBy={string.Join(",", PredictedBy)}";
}
}
}

Просмотреть файл

@ -1,38 +0,0 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
using System.Collections.Generic;
using System.Linq;
namespace Microsoft.Build.Prediction
{
/// <summary>
/// Comparer class for <see cref="BuildOutputDirectory"/>.
/// </summary>
internal sealed class BuildOutputDirectoryComparer : IEqualityComparer<BuildOutputDirectory>
{
public bool Equals(BuildOutputDirectory x, BuildOutputDirectory y)
{
if (ReferenceEquals(x, y))
{
return true;
}
if (x is null || y is null)
{
return false;
}
return x.PredictedBy.Count == y.PredictedBy.Count &&
PathComparer.Instance.Equals(x.Path, y.Path) &&
x.PredictedBy.All(p => y.PredictedBy.Contains(p));
}
public int GetHashCode(BuildOutputDirectory obj)
{
return PathComparer.Instance.GetHashCode(obj.Path) ^
(obj.PredictedBy.Count == 0 ? 0xAAAAAA : obj.PredictedBy.Count * 0xBB) ^
obj.PredictedBy.Aggregate(0, (current, s) => current ^ s.GetHashCode());
}
}
}

Просмотреть файл

@ -1,25 +0,0 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
import { NetFx } from "Sdk.BuildXL";
import * as BuildXLSdk from "Sdk.BuildXL";
import * as Managed from "Sdk.Managed";
import * as MSBuild from "Sdk.Selfhost.MSBuild";
namespace BuildPrediction {
export declare const qualifier : MSBuild.MSBuildQualifier;
@@public
export const dll = BuildXLSdk.library({
assemblyName: "Microsoft.Build.Prediction",
generateLogs: false,
sources: globR(d`.`, "*.cs"),
references: [
...MSBuild.msbuildReferences,
...MSBuild.msbuildLocatorReferences,
],
internalsVisibleTo: [
"BuildPredictionTests",
],
});
}

Просмотреть файл

@ -1,34 +0,0 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
using System;
namespace Microsoft.Build.Prediction
{
/// <summary>
/// Code helpers.
/// </summary>
internal static class Extensions
{
public static T ThrowIfNull<T>(this T val, string valName)
where T : class
{
if (val == null)
{
throw new ArgumentNullException(valName);
}
return val;
}
public static string ThrowIfNullOrEmpty(this string val, string valName)
{
if (string.IsNullOrEmpty(val))
{
throw new ArgumentNullException(valName);
}
return val;
}
}
}

Просмотреть файл

@ -1,54 +0,0 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
using Microsoft.Build.Evaluation;
using Microsoft.Build.Execution;
namespace Microsoft.Build.Prediction
{
/// <summary>
/// Implementations of this interface are run in parallel against a single evaluated MSBuild Project
/// file to predict, prior to execution of a build, file, directory/folder, and glob patterns for
/// build inputs, and output directories written by the project.
///
/// The resulting inputs, if any, are intended to feed into build caching algorithms to provide an
/// initial hash for cache lookups. Inputs need not be 100% complete on a per-project basis, but
/// more accuracy and completeness leads to better cache performance.The output directories provide
/// guidance to build execution sandboxing to allow better static analysis of the effects of
/// executing the Project.
/// </summary>
public interface IProjectStaticPredictor
{
/// <summary>
/// Performs static prediction of build inputs and outputs for use by caching and sandboxing.
/// This method may be executing on multiple threads simultaneously and should act as a
/// pure method transforming its inputs into zero or more predictions in a thread-safe
/// and idempotent fashion.
/// </summary>
/// <param name="project">The MSBuild <see cref="Microsoft.Build.Evaluation.Project"/> to use for predictions.</param>
/// <param name="projectInstance">
/// A <see cref="Microsoft.Build.Execution.ProjectInstance"/> derived from the the Project.
/// </param>
/// <param name="repositoryRootDirectory">
/// The filesystem directory containing the source code of the repository. For Git submodules
/// this is typically the directory of the outermost containing repository.
/// </param>
/// <param name="predictions">
/// A <see cref="StaticPredictions"/> instance, whose collections can be empty. This value is allowed
/// to be null which indicates an empty set result. This value should be null when returning false
/// from this method.
/// </param>
/// <returns>
/// True if the predictor found inputs or outputs to predict. When false,
/// <paramref name="predictions"/> should be null.
/// </returns>
/// <remarks>
/// Non-async since this should not require I/O, just CPU when examining the Project.
/// </remarks>
bool TryPredictInputsAndOutputs(
Project project,
ProjectInstance projectInstance,
string repositoryRootDirectory,
out StaticPredictions predictions);
}
}

Просмотреть файл

@ -1,33 +0,0 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
namespace Microsoft.Build.Prediction
{
/// <summary>
/// Targets from imported files with before/after targets.
/// </summary>
internal class ImportedTargetsWithBeforeAfterTargets
{
public ImportedTargetsWithBeforeAfterTargets(string targetName, string beforeTargets, string afterTargets)
{
TargetName = targetName;
BeforeTargets = beforeTargets;
AfterTargets = afterTargets;
}
/// <summary>
/// Gets the target's name.
/// </summary>
public string TargetName { get; }
/// <summary>
/// Gets an MSBuild list-string of AfterTargets for this target.
/// </summary>
public string AfterTargets { get; }
/// <summary>
/// Gets an MSBuild list-string of BeforeTargets for this target.
/// </summary>
public string BeforeTargets { get; }
}
}

Просмотреть файл

@ -1,58 +0,0 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
using System.IO;
using Microsoft.Build.Locator;
namespace Microsoft.Build.Prediction
{
/// <summary>
/// Utility class that uses MSBuildLocator to find the current MSBuild
/// binaries and SDKs needed for testing.
/// </summary>
internal static class MsBuildEnvironment
{
private static readonly object _lock = new object();
private static bool _alreadyExecuted;
/// <summary>
/// Sets up the appdomain. Idempotent and thread-safe, though if different
/// msBuildHintPaths are passed into multiple calls, the first one to get
/// the internal lock wins.
/// </summary>
/// <param name="msBuildHintPath">
/// When not null, this path is used to load MSBuild Microsoft.Build.* assemblies if
/// they have not already been loaded into the appdomain. When null, the fallback
/// is to look for an installed copy of Visual Studio on Windows.
/// </param>
public static void Setup(string msBuildHintPath)
{
if (_alreadyExecuted)
{
return;
}
lock (_lock)
{
if (_alreadyExecuted)
{
return;
}
if (MSBuildLocator.CanRegister)
{
if (!string.IsNullOrEmpty(msBuildHintPath) && Directory.Exists(msBuildHintPath))
{
MSBuildLocator.RegisterMSBuildPath(msBuildHintPath);
}
else
{
MSBuildLocator.RegisterDefaults();
}
}
_alreadyExecuted = true;
}
}
}
}

Просмотреть файл

@ -1,232 +0,0 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Build.Evaluation;
using Microsoft.Build.Exceptions;
using Microsoft.Build.Execution;
namespace Microsoft.Build.Prediction
{
/// <summary>
/// Helper methods for working with the MSBuild object model.
/// </summary>
internal static class MsBuildHelpers
{
/// <summary>
/// A static list containing the top-level Build target within which we evaluate
/// the MSBuild build graph.
/// </summary>
public static readonly string[] BuildTargetAsCollection = { "Build" };
/// <summary>
/// MSBuild include list-string delimiters.
/// </summary>
/// <remarks>
/// These are common split characters for dealing with MSBuild string-lists of the form
/// 'item1;item2;item3', ' item1 ; \n\titem2 ; \r\n item3', and so forth.
/// </remarks>
private static readonly char[] IncludeDelimiters = { ';', '\n', '\r', '\t' };
/// <summary>
/// Splits a given file list based on delimiters into a size-optimized list.
/// If you only need an Enumerable, use <see cref="SplitStringListEnumerable" />.
/// </summary>
/// <param name="stringList">
/// An MSBuild string-list, where whitespace is ignored and the semicolon ';' is used as a separator.
/// </param>
/// <returns>A size-optimized list of strings resulting from parsing the string-list.</returns>
public static IList<string> SplitStringList(this string stringList)
{
string[] split = stringList.Trim().Split(IncludeDelimiters, StringSplitOptions.RemoveEmptyEntries);
var splitList = new List<string>(split.Length);
foreach (string s in split)
{
string trimmed = s.Trim();
if (trimmed.Length > 0)
{
splitList.Add(trimmed);
}
}
return splitList;
}
/// <summary>
/// Splits a given file list based on delimiters into an enumerable.
/// If you need a size-optimized list, use <see cref="SplitStringList"/>.
/// </summary>
/// <param name="stringList">
/// An MSBuild string-list, where whitespace is ignored and the semicolon ';' is used as a separator.
/// </param>
/// <returns>A size-optimized list of strings resulting from parsing the string-list.</returns>
public static IEnumerable<string> SplitStringListEnumerable(this string stringList)
{
return stringList.Trim().Split(IncludeDelimiters, StringSplitOptions.RemoveEmptyEntries)
.Select(s => s.Trim())
.Where(s => s.Length > 0);
}
/// <summary>
/// Evaluates a given value in a project's context.
/// </summary>
/// <param name="project">The MSBuild Project to use for the evaluation context.</param>
/// <param name="unevaluatedValue">Unevaluated value.</param>
/// <returns>List of evaluated values.</returns>
public static IEnumerable<string> EvaluateValue(this Project project, string unevaluatedValue)
{
if (string.IsNullOrWhiteSpace(unevaluatedValue))
{
return Enumerable.Empty<string>();
}
string evaluated = project.ExpandString(unevaluatedValue);
return SplitStringListEnumerable(evaluated);
}
/// <summary>
/// Given string of semicolon-separated target names, gets the set of targets that are to be executed,
/// given targets plus all targets that given targets depends on.
/// </summary>
/// <param name="project">An MSBuild Project instance to use for context.</param>
/// <param name="targets">Semicolon separated list of targets that we should analyze.</param>
/// <param name="activeTargets">Collection into which targets should be added.</param>
public static void AddToActiveTargets(this Project project, string targets, Dictionary<string, ProjectTargetInstance> activeTargets)
{
AddToActiveTargets(project, targets.SplitStringList(), activeTargets);
}
/// <summary>
/// Given list of target names, gets set of targets that are to be executed,
/// for the provided target names and all targets that those depend on.
/// </summary>
/// <param name="project">An MSBuild Project instance to use for context.</param>
/// <param name="evaluatedTargetNames">Previously split set of evaluated target names that we should analyze.</param>
/// <param name="activeTargets">Collection into which targets should be added.</param>
public static void AddToActiveTargets(
this Project project,
IEnumerable<string> evaluatedTargetNames,
Dictionary<string, ProjectTargetInstance> activeTargets)
{
foreach (string targetName in evaluatedTargetNames)
{
// Avoid circular dependencies
if (activeTargets.ContainsKey(targetName))
{
continue;
}
// The Project or its includes might not actually include the target name.
if (project.Targets.TryGetValue(targetName, out ProjectTargetInstance target))
{
activeTargets.Add(targetName, target);
// Parse all parent targets that current target depends on.
AddToActiveTargets(project, project.EvaluateValue(target.DependsOnTargets), activeTargets);
}
}
}
public static List<ImportedTargetsWithBeforeAfterTargets> GetImportedTargetsWithBeforeAfterTargets(this Project project)
{
return project.Imports
.SelectMany(import => import.ImportedProject.Targets)
.Select(
target => new ImportedTargetsWithBeforeAfterTargets(
target.Name,
target.BeforeTargets,
target.AfterTargets))
.ToList();
}
/// <summary>
/// Expand (recursively) set of active targets to include targets which reference any
/// of the active targets with BeforeTarget or AfterTarget.
/// </summary>
/// <param name="project">An MSBuild Project instance to use for context.</param>
/// <param name="activeTargets">
/// Set of active targets. Will be modified in place to add targets that reference this
/// graph with BeforeTarget or AfterTarget.
/// </param>
public static void AddBeforeAndAfterTargets(this Project project, Dictionary<string, ProjectTargetInstance> activeTargets)
{
List<ImportedTargetsWithBeforeAfterTargets> allTargets = project.GetImportedTargetsWithBeforeAfterTargets();
var newTargetsToConsider = true;
while (newTargetsToConsider)
{
newTargetsToConsider = false;
foreach (ImportedTargetsWithBeforeAfterTargets target in allTargets)
{
// If the target exists in our project and is not already in our list of active targets ...
if (project.Targets.ContainsKey(target.TargetName)
&& !activeTargets.ContainsKey(target.TargetName))
{
IEnumerable<string> hookedTargets = project.EvaluateValue(target.AfterTargets)
.Concat(project.EvaluateValue(target.BeforeTargets));
foreach (string hookedTarget in hookedTargets)
{
// ... and it hooks a running target with BeforeTargets/AfterTargets ...
if (activeTargets.ContainsKey(hookedTarget))
{
// ... then add it to the list of running targets ...
project.AddToActiveTargets(target.TargetName, activeTargets);
// ... and make a note to run again, since activeTargets has changed.
newTargetsToConsider = true;
}
}
}
}
}
}
/// <summary>
/// Evaluates a condition in the context of a flattened ProjectInstance,
/// avoiding processing where the condition contains constructs that are
/// difficult to evaluate statically.
/// </summary>
/// <returns>
/// If condition true or false. If any MSBuild project exception is thrown during evaluation,
/// result will be false.
/// </returns>
/// <exception cref="InvalidProjectFileException">
/// Thrown by MSBuild when evaluation fails. This exception cannot be easily
/// handled locally, e.g. by catching and returning false, as it will usually
/// leave the Project in a bad state, e.g. an empty Targets collection,
/// which will affect downstream code that tries to evaluate targets.
/// </exception>
public static bool EvaluateConditionCarefully(this ProjectInstance projectInstance, string condition)
{
// To avoid extra work, return true (default) if condition is empty.
if (string.IsNullOrWhiteSpace(condition))
{
return true;
}
// We cannot handle %(...) metadata accesses in conditions. For example, see these conditions
// in Microsoft.WebApplication.targets:
//
// <Copy SourceFiles="@(Content)" Condition="'%(Content.Link)' == ''"
// <Copy SourceFiles="@(Content)" Condition="!$(DisableLinkInCopyWebApplication) And '%(Content.Link)' != ''"
//
// Attempting to evaluate these conditions throws an MSB4191 exception from
// Project.ReevaluateIfNecessary(), trashing Project (Project.Targets collection
// becomes empty, for example). Extra info at:
// http://stackoverflow.com/questions/4721879/ms-build-access-compiler-settings-in-a-subsequent-task
// ProjectInstance.EvaluateCondition() also does not support bare metadata based condition parsing,
// it uses the internal Expander class with option ExpandPropertiesAndItems but not the
// more extensive ExpandAll or ExpandMetadata.
// https://github.com/Microsoft/msbuild/blob/master/src/Build/Instance/ProjectInstance.cs#L1763
if (condition.Contains("%("))
{
return false;
}
return projectInstance.EvaluateCondition(condition);
}
}
}

Просмотреть файл

@ -1,41 +0,0 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
using System;
namespace Microsoft.Build.Prediction
{
/// <summary>
/// Gets the appdomain-wide appropriate filesystem path string comparer
/// appropriate to the current operating system.
/// </summary>
internal static class PathComparer
{
public static readonly StringComparer Instance = GetPathComparer();
public static readonly StringComparison Comparison = GetPathComparison();
private static StringComparer GetPathComparer()
{
switch (Environment.OSVersion.Platform)
{
case PlatformID.MacOSX:
case PlatformID.Unix:
return StringComparer.Ordinal;
default:
return StringComparer.OrdinalIgnoreCase;
}
}
private static StringComparison GetPathComparison()
{
switch (Environment.OSVersion.Platform)
{
case PlatformID.MacOSX:
case PlatformID.Unix:
return StringComparison.Ordinal;
default:
return StringComparison.OrdinalIgnoreCase;
}
}
}
}

Просмотреть файл

@ -1,146 +0,0 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.Build.Evaluation;
using Microsoft.Build.Execution;
namespace Microsoft.Build.Prediction
{
/// <summary>
/// Executes a set of <see cref="IProjectStaticPredictor"/> instances against
/// a <see cref="Microsoft.Build.Evaluation.Project"/> instance, aggregating
/// the result.
/// </summary>
public sealed class ProjectStaticPredictionExecutor
{
private readonly string _repositoryRootDirectory;
private readonly PredictorAndName[] _predictors;
/// <summary>Initializes a new instance of the <see cref="ProjectStaticPredictionExecutor"/> class.</summary>
/// <param name="repositoryRootDirectory">
/// The filesystem directory containing the source code of the repository. For Git submodules
/// this is typically the directory of the outermost containing repository.
/// This is used for normalization of predicted paths.
/// </param>
/// <param name="predictors">The set of <see cref="IProjectStaticPredictor"/> instances to use for prediction.</param>
public ProjectStaticPredictionExecutor(
string repositoryRootDirectory,
IEnumerable<IProjectStaticPredictor> predictors)
{
_repositoryRootDirectory = repositoryRootDirectory.ThrowIfNullOrEmpty(nameof(repositoryRootDirectory));
_predictors = predictors
.ThrowIfNull(nameof(predictors))
.Select(p => new PredictorAndName(p))
.ToArray(); // Array = faster parallel performance.
}
/// <summary>
/// Executes all predictors in parallel against the provided Project and aggregates
/// the results into one set of predictions. All paths in the final predictions are
/// fully qualified paths, not relative to the directory containing the Project or
/// to the repository root directory, since inputs and outputs could lie outside of
/// that directory.
/// </summary>
/// <returns>An object describing all predicted inputs and outputs.</returns>
public StaticPredictions PredictInputsAndOutputs(Project project)
{
project.ThrowIfNull(nameof(project));
// Squash the Project with its full XML contents and tracking down to
// a more memory-efficient format that can be used to evaluate conditions.
// TODO: Static Graph needs to provide both, not just ProjectInstance, when we integrate.
ProjectInstance projectInstance = project.CreateProjectInstance(ProjectInstanceSettings.ImmutableWithFastItemLookup);
// Perf: Compared ConcurrentQueue vs. static array of results,
// queue is 25% slower when all predictors return empty results,
// ~25% faster as predictors return more and more false/null results,
// with the breakeven point in the 10-15% null range.
// ConcurrentBag 10X worse than either of the above, ConcurrentStack about the same.
// Keeping queue implementation since many predictors return false.
var results = new ConcurrentQueue<StaticPredictions>();
Parallel.For(
0,
_predictors.Length,
i =>
{
bool success = _predictors[i].Predictor.TryPredictInputsAndOutputs(
project,
projectInstance,
_repositoryRootDirectory,
out StaticPredictions result);
// Tag each prediction with its source.
// Check for null even on success as a bad predictor could do that.
if (success && result != null)
{
foreach (BuildInput item in result.BuildInputs)
{
item.AddPredictedBy(_predictors[i].TypeName);
}
foreach (BuildOutputDirectory item in result.BuildOutputDirectories)
{
item.AddPredictedBy(_predictors[i].TypeName);
}
results.Enqueue(result);
}
});
var inputsByPath = new Dictionary<string, BuildInput>(PathComparer.Instance);
var outputDirectoriesByPath = new Dictionary<string, BuildOutputDirectory>(PathComparer.Instance);
foreach (StaticPredictions predictions in results)
{
// TODO: Determine policy when dup inputs vary by IsDirectory.
foreach (BuildInput input in predictions.BuildInputs)
{
if (inputsByPath.TryGetValue(input.Path, out BuildInput existingInput))
{
existingInput.AddPredictedBy(input.PredictedBy);
}
else
{
inputsByPath[input.Path] = input;
}
}
foreach (BuildOutputDirectory outputDir in predictions.BuildOutputDirectories)
{
if (outputDirectoriesByPath.TryGetValue(outputDir.Path, out BuildOutputDirectory existingOutputDir))
{
existingOutputDir.AddPredictedBy(outputDir.PredictedBy);
}
else
{
outputDirectoriesByPath[outputDir.Path] = outputDir;
}
}
}
return new StaticPredictions(inputsByPath.Values, outputDirectoriesByPath.Values);
}
private readonly struct PredictorAndName
{
public readonly IProjectStaticPredictor Predictor;
/// <summary>
/// Cached type name - we expect predictor instances to be reused many times in
/// an overall parsing session, avoid doing the reflection over and over in
/// the prediction methods.
/// </summary>
public readonly string TypeName;
public PredictorAndName(IProjectStaticPredictor predictor)
{
Predictor = predictor;
TypeName = predictor.GetType().Name;
}
}
}
}

Просмотреть файл

@ -1,44 +0,0 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;
namespace Microsoft.Build.Prediction
{
/// <summary>
/// Creates instances of <see cref="IProjectStaticPredictor"/> for use with
/// <see cref="ProjectStaticPredictionExecutor"/>.
/// </summary>
public static class ProjectStaticPredictorFactory
{
/// <summary>
/// Creates a collection of all instances of <see cref="IProjectStaticPredictor"/> contained in the Microsoft.Build.Prediction assembly.
/// </summary>
/// <param name="msBuildHintPath">
/// When not null, this path is used to load MSBuild Microsoft.Build.* assemblies if
/// they have not already been loaded into the appdomain. When null, the fallback
/// is to look for an installed copy of Visual Studio on Windows.
/// </param>
/// <returns>A collection of <see cref="IProjectStaticPredictor"/>.</returns>
public static IReadOnlyCollection<IProjectStaticPredictor> CreateStandardPredictors(string msBuildHintPath)
{
// Ensure the user has MSBuild loaded into the appdomain.
MsBuildEnvironment.Setup(msBuildHintPath);
Type[] types = Assembly.GetExecutingAssembly().GetTypes()
.Where(t => !t.IsAbstract && !t.IsInterface && t.GetInterfaces().Contains(typeof(IProjectStaticPredictor)))
.ToArray();
var predictors = new IProjectStaticPredictor[types.Length];
// Use default constructor.
Parallel.For(0, types.Length, i =>
predictors[i] = (IProjectStaticPredictor)Activator.CreateInstance(types[i]));
return predictors;
}
}
}

Просмотреть файл

@ -1,59 +0,0 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
using System;
using System.Collections.Generic;
using System.Linq;
using System.IO;
using Microsoft.Build.Evaluation;
using Microsoft.Build.Execution;
namespace Microsoft.Build.Prediction.StandardPredictors
{
/// <summary>
/// Generates inputs from all globally scoped MSBuild Items whose types
/// are listed in AvailableItemName metadata.
/// </summary>
/// <remarks>
/// AvailableItemNames are usually used for integration with Visual Studio,
/// see https://docs.microsoft.com/en-us/visualstudio/msbuild/visual-studio-integration-msbuild?view=vs-2017 ,
/// but they are a useful shorthand for finding file items.
///
/// As an example, for vcxproj projects the ClCompile item name is listed
/// as an AvailableItemName by Microsoft.CppCommon.targets.
///
/// Interestingly, C# Compile items have no AvailableItemName in their associated .targets file.
/// </remarks>
public class AvailableItemNameItems : IProjectStaticPredictor
{
internal const string AvailableItemName = "AvailableItemName";
/// <inheritdoc/>
public bool TryPredictInputsAndOutputs(
Project project,
ProjectInstance projectInstance,
string repositoryRootDirectory,
out StaticPredictions predictions)
{
// TODO: Need to determine how to normalize evaluated include selected below and determine if it is relative to project.
var availableItemNames = new HashSet<string>(
project.GetItems(AvailableItemName).Select(item => item.EvaluatedInclude),
StringComparer.OrdinalIgnoreCase);
List<BuildInput> itemInputs = availableItemNames.SelectMany(
availableItemName => project.GetItems(availableItemName).Select(
item => new BuildInput(
Path.Combine(project.DirectoryPath, item.EvaluatedInclude),
isDirectory: false)))
.ToList();
if (itemInputs.Count > 0)
{
predictions = new StaticPredictions(itemInputs, null);
return true;
}
predictions = null;
return false;
}
}
}

Просмотреть файл

@ -1,43 +0,0 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Microsoft.Build.Evaluation;
using Microsoft.Build.Execution;
namespace Microsoft.Build.Prediction.StandardPredictors
{
/// <summary>
/// Finds Compile items, typically but not necessarily always from csproj files, as inputs.
/// </summary>
public class CSharpCompileItems : IProjectStaticPredictor
{
internal const string CompileItemName = "Compile";
/// <inheritdoc/>
public bool TryPredictInputsAndOutputs(
Project project,
ProjectInstance projectInstance,
string repositoryRootDirectory,
out StaticPredictions predictions)
{
// TODO: Need to determine how to normalize evaluated include selected below and determine if it is relative to project.
List<BuildInput> itemInputs = project.GetItems(CompileItemName)
.Select(item => new BuildInput(
Path.Combine(project.DirectoryPath, item.EvaluatedInclude),
isDirectory: false))
.ToList();
if (itemInputs.Count > 0)
{
predictions = new StaticPredictions(itemInputs, buildOutputDirectories: null);
return true;
}
predictions = null;
return false;
}
}
}

Просмотреть файл

@ -1,226 +0,0 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Microsoft.Build.Evaluation;
using Microsoft.Build.Execution;
namespace Microsoft.Build.Prediction.StandardPredictors.CopyTask
{
/// <summary>
/// Parses Copy tasks from Targets in the provided Project to predict inputs
/// and outputs.
/// </summary>
/// <remarks>
/// This predictor assumes that the Build target is the primary for MSBuild evaluation,
/// and follows the Targets activated by that target, along with all custom Targets
/// present in the current project file.
/// </remarks>
public class CopyTaskPredictor : IProjectStaticPredictor
{
private const string CopyTaskName = "Copy";
private const string CopyTaskSourceFiles = "SourceFiles";
private const string CopyTaskDestinationFiles = "DestinationFiles";
private const string CopyTaskDestinationFolder = "DestinationFolder";
/// <inheritdoc />
public bool TryPredictInputsAndOutputs(
Project project,
ProjectInstance projectInstance,
string repositoryRootDirectory,
out StaticPredictions predictions)
{
// Determine the active Targets in this Project.
var activeTargets = new Dictionary<string, ProjectTargetInstance>(StringComparer.OrdinalIgnoreCase);
// Start with the default Build target and all of its parent targets, the closure of its dependencies.
project.AddToActiveTargets(MsBuildHelpers.BuildTargetAsCollection, activeTargets);
// Aside from InitialTargets and DefaultTargets, for completeness of inputs/outputs detection,
// include custom targets defined directly in this Project.
// Note that this misses targets defined in any custom targets files.
foreach (ProjectTargetInstance target in projectInstance.Targets.Values
.Where(t => string.Equals(t.Location.File, project.ProjectFileLocation.File, PathComparer.Comparison)))
{
project.AddToActiveTargets(new[] { target.Name }, activeTargets);
}
project.AddBeforeAndAfterTargets(activeTargets);
// Then parse copy tasks for these targets.
var buildInputs = new HashSet<BuildInput>(BuildInput.ComparerInstance);
var buildOutputDirectories = new HashSet<string>(PathComparer.Instance);
foreach (KeyValuePair<string, ProjectTargetInstance> target in activeTargets)
{
ParseCopyTask(target.Value, projectInstance, buildInputs, buildOutputDirectories);
}
if (buildInputs.Count > 0)
{
predictions = new StaticPredictions(
buildInputs,
buildOutputDirectories.Select(o => new BuildOutputDirectory(o)).ToList());
return true;
}
predictions = null;
return false;
}
/// <summary>
/// Parses the input and output files for copy tasks of given target.
/// </summary>
private static void ParseCopyTask(
ProjectTargetInstance target,
ProjectInstance projectInstance,
HashSet<BuildInput> buildInputs,
HashSet<string> buildOutputDirectories)
{
// 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.Expressions.Count == 0)
{
continue;
}
buildInputs.UnionWith(inputs.DedupedFiles.
Select(file => new BuildInput(Path.Combine(projectInstance.Directory, file), false)));
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.Expressions.Count == 1 &&
inputs.NumBatchExpressions == 1 && inputs.Expressions.Count == 1) ||
(outputs.NumBatchExpressions == 0 && inputs.NumBatchExpressions == 0))
{
ProcessOutputs(projectInstance.FullPath, inputs, outputs, hasDestinationFolder, buildOutputDirectories);
}
else
{
// Ignore case we cannot handle.
}
}
else
{
// Ignore malformed case.
}
}
}
}
}
/// <summary>
/// Validates that a task's outputs are sane. If so, predicts output directories.
/// </summary>
/// <param name="projectFullPath">The absolute path to the project instance. Can be null if the project was not loaded
/// from this</param>
/// <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="buildOutputDirectories">Collection to fill with output folder predictions.</param>
private static void ProcessOutputs(
string projectFullPath,
FileExpressionList inputs,
FileExpressionList outputs,
bool copyTaskSpecifiesDestinationFolder,
HashSet<string> buildOutputDirectories)
{
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]);
}
// If the predicted directory is not absolute, let's try to make it absolute using the project full path
if (!TryMakePathAbsoluteIfNeeded(predictedOutputDirectory, projectFullPath, out string absolutePathPrediction))
{
// The project full path is not available, so just ignore this prediction
continue;
}
buildOutputDirectories.Add(absolutePathPrediction);
}
}
private static bool TryMakePathAbsoluteIfNeeded(string path, string projectFullPath, out string absolutePath)
{
if (!Path.IsPathRooted(path))
{
if (string.IsNullOrEmpty(projectFullPath))
{
absolutePath = string.Empty;
return false;
}
absolutePath = Path.GetFullPath(Path.Combine(Path.GetDirectoryName(projectFullPath), path));
return true;
}
absolutePath = path;
return true;
}
}
}

Просмотреть файл

@ -1,61 +0,0 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
using System;
using System.Collections.Generic;
using System.IO;
using Microsoft.Build.Execution;
namespace Microsoft.Build.Prediction.StandardPredictors.CopyTask
{
/// <summary>
/// The abstract base class of an MSBuild item expression (e.g. @(Compile), %(Content.Filename), etc.)
/// </summary>
internal abstract class FileExpressionBase
{
/// <summary>
/// Initializes a new instance of the <see cref="FileExpressionBase"/> class.
/// </summary>
/// <param name="rawExpression">An unprocessed string for a single expression.</param>
/// <param name="project">The project where the expression exists.</param>
/// <param name="task">The task where the expression exists.</param>
protected FileExpressionBase(string rawExpression, ProjectInstance project, ProjectTaskInstance task = null)
{
string trimmedExpression = rawExpression.Trim();
if (task != null)
{
string copyTaskFilePath = task.Location.File;
// Process MsBuildThis* macros
// ignore copy tasks within the proj - evaluation will just work.
if (!copyTaskFilePath.Equals(project.FullPath, StringComparison.OrdinalIgnoreCase))
{
// We leave off the trailing ')' to allow for macro operations. This could allow us to misdetect macros
// (e.g. $(MsBuildThisFileButNotReally), but should be rare and should still function correctly even if we
// do.
if (trimmedExpression.IndexOf("$(MSBuildThisFile", StringComparison.OrdinalIgnoreCase) >= 0)
{
#pragma warning disable CA1308 // Normalize strings to uppercase
trimmedExpression = trimmedExpression.ToLowerInvariant();
#pragma warning restore CA1308 // Normalize strings to uppercase
trimmedExpression = trimmedExpression.Replace("$(msbuildthisfiledirectory)", Path.GetDirectoryName(copyTaskFilePath) + "\\");
trimmedExpression = trimmedExpression.Replace("$(msbuildthisfile)", Path.GetFileName(copyTaskFilePath));
trimmedExpression = trimmedExpression.Replace("$(msbuildthisfileextension)", Path.GetExtension(copyTaskFilePath));
trimmedExpression = trimmedExpression.Replace("$(msbuildthisfilefullpath)", copyTaskFilePath);
trimmedExpression = trimmedExpression.Replace("$(msbuildthisfilename)", Path.GetFileNameWithoutExtension(copyTaskFilePath));
}
}
}
ProcessedExpression = trimmedExpression;
}
/// <summary>
/// Gets or sets the set of all files in the evaluated expression.
/// </summary>
public IEnumerable<string> EvaluatedFiles { get; protected set; }
protected string ProcessedExpression { get; }
}
}

Просмотреть файл

@ -1,58 +0,0 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
using System;
using System.Text.RegularExpressions;
using Microsoft.Build.Execution;
namespace Microsoft.Build.Prediction.StandardPredictors.CopyTask
{
/// <summary>
/// Class for a batch expression, e.g. '%(Compile.fileName).%(Compile.extension))'.
/// </summary>
internal class FileExpressionBatched : FileExpressionBase
{
private static readonly Regex BatchedItemRegex = new Regex(@"%\((?<ItemType>[^\.\)]+)\.?(?<Metadata>[^\).]*?)\)", RegexOptions.Compiled);
/// <summary>
/// Initializes a new instance of the <see cref="FileExpressionBatched"/> class.
/// </summary>
/// <param name="rawExpression">An unprocessed string for a single expression.</param>
/// <param name="project">The project where the expression exists.</param>
/// <param name="task">The task where the expression exists.</param>
public FileExpressionBatched(string rawExpression, ProjectInstance project, ProjectTaskInstance task = null)
: base(rawExpression, project, task)
{
// Copy task has batching in it. Get the batched items if possible, then parse inputs.
Match regexMatch = BatchedItemRegex.Match(ProcessedExpression);
if (regexMatch.Success)
{
// If the user didn't specify a metadata item, then we default to Identity
string transformItem =
string.IsNullOrEmpty(regexMatch.Groups[2].Value) ?
BatchedItemRegex.Replace(ProcessedExpression, @"%(Identity)") :
BatchedItemRegex.Replace(ProcessedExpression, @"%($2)");
// Convert the batch into a transform. If this is an item -> metadata based transition then it will do the replacements for you.
string expandedString = project.ExpandString(
$"@({regexMatch.Groups["ItemType"].Value}-> '{transformItem}')");
EvaluatedFiles = expandedString.SplitStringList();
}
else
{
throw new InvalidOperationException();
}
}
/// <summary>
/// Determines if an expression is a batch expression.
/// </summary>
/// <param name="trimmedExpression">The Expression string with leading and trailing whitespace removed.</param>
/// <returns>True if trimmedExpression is a batch Expression.</returns>
public static bool IsExpression(string trimmedExpression)
{
return !FileExpressionTransform.IsExpression(trimmedExpression) &&
trimmedExpression.IndexOf("%(", StringComparison.Ordinal) >= 0;
}
}
}

Просмотреть файл

@ -1,39 +0,0 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
using Microsoft.Build.Execution;
namespace Microsoft.Build.Prediction.StandardPredictors.CopyTask
{
/// <summary>
/// Factory class that instantiates FileExpression classes. File expressions are MSBuild expressions that
/// evaluate to a list of files (e.g. @(Compile -> '%(filename)') or %(None.filename)).
/// </summary>
internal static class FileExpressionFactory
{
/// <summary>
/// Factory method that parses an unprocessed Expression string and returns an Expression
/// of the appropriate type.
/// </summary>
/// <param name="rawExpression">An unprocessed string for a single expression.</param>
/// <param name="project">The project where the expression exists.</param>
/// <param name="task">The task where the expression exists.</param>
/// <returns>A file expression class.</returns>
public static FileExpressionBase ParseExpression(string rawExpression, ProjectInstance project, ProjectTaskInstance task)
{
string trimmedExpression = rawExpression.Trim();
if (FileExpressionBatched.IsExpression(trimmedExpression))
{
return new FileExpressionBatched(rawExpression, project, task);
}
if (FileExpressionTransform.IsExpression(rawExpression))
{
return new FileExpressionTransform(trimmedExpression, project, task);
}
return new FileExpressionLiteral(rawExpression, project, task);
}
}
}

Просмотреть файл

@ -1,78 +0,0 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
using System.Collections.Generic;
using System.Linq;
using Microsoft.Build.Execution;
namespace Microsoft.Build.Prediction.StandardPredictors.CopyTask
{
/// <summary>
/// Contains a parsed list of file expressions as well as the list of files derived from evaluating said
/// expressions.
/// </summary>
internal class FileExpressionList
{
/// <summary>
/// Initializes a new instance of the <see cref="FileExpressionList"/> class.
/// </summary>
/// <param name="rawFileListString">The unprocessed list of file expressions.</param>
/// <param name="project">The project where the expression list exists.</param>
/// <param name="task">The task where the expression list exists.</param>
public FileExpressionList(string rawFileListString, ProjectInstance project, ProjectTaskInstance task)
{
IList<string> expressions = rawFileListString.SplitStringList();
var seenFiles = new HashSet<string>(PathComparer.Instance);
foreach (string expression in expressions)
{
FileExpressionBase parsedExpression = FileExpressionFactory.ParseExpression(expression, project, task);
Expressions.Add(parsedExpression);
foreach (string file in parsedExpression.EvaluatedFiles)
{
if (string.IsNullOrWhiteSpace(file))
{
continue;
}
if (seenFiles.Add(file))
{
DedupedFiles.Add(file);
}
AllFiles.Add(file);
}
}
}
/// <summary>
/// Gets the set of all expressions in the file list string.
/// </summary>
public List<FileExpressionBase> Expressions { get; } = new List<FileExpressionBase>();
/// <summary>
/// Gets the set of all files in all of the expanded expressions. May include duplicates.
/// </summary>
public List<string> AllFiles { get; } = new List<string>();
/// <summary>
/// Gets the set of all files in the expanded expressions. Duplicates are removed.
/// </summary>
public List<string> DedupedFiles { get; } = new List<string>();
/// <summary>
/// Gets the number of batch expressions in the file list.
/// </summary>
public int NumBatchExpressions => Expressions.Count((FileExpressionBase expression) => expression.GetType() == typeof(FileExpressionBatched));
/// <summary>
/// Gets the number of literal expressions in the file list.
/// </summary>
public int NumLiteralExpressions => Expressions.Count((FileExpressionBase expression) => expression.GetType() == typeof(FileExpressionLiteral));
/// <summary>
/// Gets the number of transform expressions in the file list.
/// </summary>
public int NumTransformExpressions => Expressions.Count((FileExpressionBase expression) => expression.GetType() == typeof(FileExpressionTransform));
}
}

Просмотреть файл

@ -1,26 +0,0 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
using Microsoft.Build.Execution;
namespace Microsoft.Build.Prediction.StandardPredictors.CopyTask
{
/// <summary>
/// Class for literal expressions, e.g. '$(Outdir)\foo.dll'.
/// </summary>
internal class FileExpressionLiteral : FileExpressionBase
{
/// <summary>
/// Initializes a new instance of the <see cref="FileExpressionLiteral"/> class.
/// </summary>
/// <param name="rawExpression">An unprocessed string for a single expression.</param>
/// <param name="project">The project where the expression exists.</param>
/// <param name="task">The task where the expression exists.</param>
public FileExpressionLiteral(string rawExpression, ProjectInstance project, ProjectTaskInstance task = null)
: base(rawExpression, project, task)
{
string expandedFileListString = project.ExpandString(ProcessedExpression);
EvaluatedFiles = expandedFileListString.SplitStringList();
}
}
}

Просмотреть файл

@ -1,35 +0,0 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
using System;
using Microsoft.Build.Execution;
namespace Microsoft.Build.Prediction.StandardPredictors.CopyTask
{
/// <summary>
/// Class for transform expressions, e.g. @(Compile -> '$(Outdir)\$(filename)') .
/// </summary>
internal class FileExpressionTransform : FileExpressionLiteral
{
/// <summary>
/// Initializes a new instance of the <see cref="FileExpressionTransform"/> class.
/// </summary>
/// <param name="rawExpression">An unprocessed string for a single expression.</param>
/// <param name="project">The project where the expression exists.</param>
/// <param name="task">The task where the expression exists.</param>
public FileExpressionTransform(string rawExpression, ProjectInstance project, ProjectTaskInstance task = null)
: base(rawExpression, project, task)
{
}
/// <summary>
/// Determines if an expression is a transform expression.
/// </summary>
/// <param name="trimmedExpression">The Expression string with leading and trailing whitespace removed.</param>
/// <returns>True if trimmedExpression is a transform Expression.</returns>
public static bool IsExpression(string trimmedExpression)
{
return trimmedExpression.StartsWith("@(", StringComparison.Ordinal);
}
}
}

Просмотреть файл

@ -1,49 +0,0 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
using System.IO;
using Microsoft.Build.Evaluation;
using Microsoft.Build.Execution;
namespace Microsoft.Build.Prediction.StandardPredictors
{
/// <summary>
/// Scrapes the $(IntermediateOutputPath) if found.
/// </summary>
internal class IntermediateOutputPathIsOutputDir : IProjectStaticPredictor
{
internal const string IntermediateOutputPathMacro = "IntermediateOutputPath";
public bool TryPredictInputsAndOutputs(
Project project,
ProjectInstance projectInstance,
string repositoryRootDirectory,
out StaticPredictions predictions)
{
string intermediateOutputPath = project.GetPropertyValue(IntermediateOutputPathMacro);
if (string.IsNullOrWhiteSpace(intermediateOutputPath))
{
// It is not defined, so we don't return a result.
predictions = null;
return false;
}
// If the path is relative, it is interpreted as relative to the project directory path
string predictedOutputDirectory;
if (!Path.IsPathRooted(intermediateOutputPath))
{
predictedOutputDirectory = Path.Combine(project.DirectoryPath, intermediateOutputPath);
}
else
{
predictedOutputDirectory = intermediateOutputPath;
}
predictions = new StaticPredictions(
buildInputs: null,
buildOutputDirectories: new[] { new BuildOutputDirectory(Path.GetFullPath(predictedOutputDirectory)) });
return true;
}
}
}

Просмотреть файл

@ -1,65 +0,0 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
using System.IO;
using Microsoft.Build.Evaluation;
using Microsoft.Build.Execution;
namespace Microsoft.Build.Prediction.StandardPredictors
{
/// <summary>
/// Scrapes the $(OutDir) or, if not found, $(OutputPath) as an output directory.
/// </summary>
internal class OutDirOrOutputPathIsOutputDir : IProjectStaticPredictor
{
internal const string OutDirMacro = "OutDir";
internal const string OutputPathMacro = "OutputPath";
public bool TryPredictInputsAndOutputs(
Project project,
ProjectInstance projectInstance,
string repositoryRootDirectory,
out StaticPredictions predictions)
{
string outDir = project.GetPropertyValue(OutDirMacro);
string outputPath = project.GetPropertyValue(OutputPathMacro);
// For an MSBuild project, the output goes to $(OutDir) by default. Usually $(OutDir)
// equals $(OutputPath). Many targets expect OutputPath/OutDir to be defined and
// MsBuild.exe reports an error if these macros are undefined.
string finalOutputPath;
if (!string.IsNullOrWhiteSpace(outDir))
{
finalOutputPath = outDir;
}
else if (!string.IsNullOrWhiteSpace(outputPath))
{
// Some projects use custom code with $(OutputPath) set instead of following the common .targets pattern.
// Fall back to $(OutputPath) first when $(OutDir) is not set.
finalOutputPath = outputPath;
}
else
{
// Neither is defined, we don't return a result.
predictions = null;
return false;
}
// If the path is relative, it is interpreted as relative to the project directory path
string predictedOutputDirectory;
if (!Path.IsPathRooted(finalOutputPath))
{
predictedOutputDirectory = Path.Combine(project.DirectoryPath, finalOutputPath);
}
else
{
predictedOutputDirectory = finalOutputPath;
}
predictions = new StaticPredictions(
buildInputs: null,
buildOutputDirectories: new[] { new BuildOutputDirectory(Path.GetFullPath(predictedOutputDirectory)) });
return true;
}
}
}

Просмотреть файл

@ -1,33 +0,0 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
using System.Collections.Generic;
using Microsoft.Build.Evaluation;
using Microsoft.Build.Execution;
namespace Microsoft.Build.Prediction.StandardPredictors
{
/// <summary>
/// Finds project filename and imports, as inputs.
/// </summary>
public class ProjectFileAndImportedFiles : IProjectStaticPredictor
{
/// <inheritdoc/>
public bool TryPredictInputsAndOutputs(Project project, ProjectInstance projectInstance, string repositoryRootDirectory, out StaticPredictions predictions)
{
var inputs = new List<BuildInput>()
{
new BuildInput(project.FullPath, false)
};
foreach (ResolvedImport import in project.Imports)
{
inputs.Add(new BuildInput(import.ImportedProject.FullPath, isDirectory: false));
}
predictions = new StaticPredictions(inputs, null);
return true;
}
}
}

Просмотреть файл

@ -1,32 +0,0 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
using System;
using System.Collections.Generic;
namespace Microsoft.Build.Prediction
{
/// <summary>
/// Predictions of build inputs and outputs provided by implementations of
/// <see cref="IProjectStaticPredictor"/>.
/// </summary>
public sealed class StaticPredictions
{
/// <summary>Initializes a new instance of the <see cref="StaticPredictions"/> class.</summary>
/// <param name="buildInputs">A collection of predicted file or directory inputs.</param>
/// <param name="buildOutputDirectories">A collection of predicted directory outputs.</param>
public StaticPredictions(
IReadOnlyCollection<BuildInput> buildInputs,
IReadOnlyCollection<BuildOutputDirectory> buildOutputDirectories)
{
BuildInputs = buildInputs ?? Array.Empty<BuildInput>();
BuildOutputDirectories = buildOutputDirectories ?? Array.Empty<BuildOutputDirectory>();
}
/// <summary>Gets a collection of predicted file or directory inputs.</summary>
public IReadOnlyCollection<BuildInput> BuildInputs { get; }
/// <summary>Gets a collection of predicted directory outputs.</summary>
public IReadOnlyCollection<BuildOutputDirectory> BuildOutputDirectories { get; }
}
}

Просмотреть файл

@ -1,39 +0,0 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
import { NetFx } from "Sdk.BuildXL";
import * as Deployment from "Sdk.Deployment";
import * as BuildXLSdk from "Sdk.BuildXL";
import * as Managed from "Sdk.Managed";
import * as MSBuild from "Sdk.Selfhost.MSBuild";
namespace Tests {
export declare const qualifier : MSBuild.MSBuildQualifier;
@@public
export const dll = BuildXLSdk.test({
assemblyName: "BuildPredictionTests",
sources: globR(d`.`, "*.cs"),
references: [
...addIf(BuildXLSdk.isFullFramework,
NetFx.System.Xml.dll
),
BuildPrediction.dll,
...MSBuild.msbuildReferences,
...MSBuild.msbuildLocatorReferences,
],
runtimeContent: [
...MSBuild.msbuildRuntimeContent,
{
subfolder: "TestsData",
contents: [
Deployment.createFromDisk(d`TestsData`)
],
}
],
runtimeContentToSkip : [
importFrom("System.Threading.Tasks.Dataflow").pkg
]
});
}

Просмотреть файл

@ -1,182 +0,0 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
using System;
using System.Diagnostics;
using Microsoft.Build.Construction;
using Microsoft.Build.Evaluation;
using Microsoft.Build.Execution;
using Xunit;
namespace Microsoft.Build.Prediction.Tests
{
public class ProjectStaticPredictionExecutorTests
{
static ProjectStaticPredictionExecutorTests()
{
MsBuildEnvironment.Setup(TestHelpers.GetAssemblyLocation());
}
[Fact]
public void EmptyPredictionsResultInEmptyAggregateResult()
{
var predictors = new IProjectStaticPredictor[]
{
new MockPredictor(new StaticPredictions(null, null)),
new MockPredictor(new StaticPredictions(null, null)),
};
var executor = new ProjectStaticPredictionExecutor(@"c:\repo", predictors);
var project = TestHelpers.CreateProjectFromRootElement(ProjectRootElement.Create());
StaticPredictions predictions = executor.PredictInputsAndOutputs(project);
Assert.NotNull(predictions);
Assert.Equal(0, predictions.BuildInputs.Count);
Assert.Equal(0, predictions.BuildOutputDirectories.Count);
}
[Fact]
public void DistinctInputsAndOutputsAreAggregated()
{
var predictors = new IProjectStaticPredictor[]
{
new MockPredictor(new StaticPredictions(
new[] { new BuildInput(@"foo\bar1", false) },
new[] { new BuildOutputDirectory(@"blah\boo1") })),
new MockPredictor2(new StaticPredictions(
new[] { new BuildInput(@"foo\bar2", false) },
new[] { new BuildOutputDirectory(@"blah\boo2") })),
};
var executor = new ProjectStaticPredictionExecutor(@"c:\repo", predictors);
var project = TestHelpers.CreateProjectFromRootElement(ProjectRootElement.Create());
StaticPredictions predictions = executor.PredictInputsAndOutputs(project);
BuildInput[] expectedInputs =
{
new BuildInput(@"foo\bar1", false, "MockPredictor"),
new BuildInput(@"foo\bar2", false, "MockPredictor2"),
};
BuildOutputDirectory[] expectedBuildOutputDirectories =
{
new BuildOutputDirectory(@"blah\boo1", "MockPredictor"),
new BuildOutputDirectory(@"blah\boo2", "MockPredictor2"),
};
predictions.AssertPredictions(expectedInputs, expectedBuildOutputDirectories);
}
[Fact]
public void DuplicateInputsAndOutputsMergePredictedBys()
{
var predictors = new IProjectStaticPredictor[]
{
new MockPredictor(new StaticPredictions(
new[] { new BuildInput(@"foo\bar", false) },
new[] { new BuildOutputDirectory(@"blah\boo") })),
new MockPredictor2(new StaticPredictions(
new[] { new BuildInput(@"foo\bar", false) },
new[] { new BuildOutputDirectory(@"blah\boo") })),
};
var executor = new ProjectStaticPredictionExecutor(@"c:\repo", predictors);
var project = TestHelpers.CreateProjectFromRootElement(ProjectRootElement.Create());
StaticPredictions predictions = executor.PredictInputsAndOutputs(project);
predictions.AssertPredictions(
new[] { new BuildInput(@"foo\bar", false, "MockPredictor", "MockPredictor2") },
new[] { new BuildOutputDirectory(@"blah\boo", "MockPredictor", "MockPredictor2") });
}
[Fact]
public void PredictorSparsenessStressTest()
{
int[] numPredictorCases = { 40 }; // Set to 1000 or more for reduced average noise when using tickResults for measurements.
int[] sparsenessPercentages = { 0, 25, 50, 75, 100 };
var tickResults = new long[numPredictorCases.Length][];
var proj = TestHelpers.CreateProjectFromRootElement(ProjectRootElement.Create());
// Run through twice and keep the second round only - first round affected by JIT overhead.
for (int iter = 0; iter < 2; iter++)
{
for (int p = 0; p < numPredictorCases.Length; p++)
{
int numPredictors = numPredictorCases[p];
tickResults[p] = new long[sparsenessPercentages.Length];
var predictors = new IProjectStaticPredictor[numPredictors];
int sparseIndex = 0;
for (int s = 0; s < sparsenessPercentages.Length; s++)
{
int sparsenessPercentage = sparsenessPercentages[s];
for (int i = 0; i < numPredictors; i++)
{
if (sparseIndex < sparsenessPercentage)
{
predictors[i] = new MockPredictor(null);
}
else
{
predictors[i] = new MockPredictor(new StaticPredictions(null, null));
}
sparseIndex++;
if (sparseIndex > sparsenessPercentage)
{
sparseIndex = 0;
}
}
var executor = new ProjectStaticPredictionExecutor(@"c:\repo", predictors);
Stopwatch sw = Stopwatch.StartNew();
executor.PredictInputsAndOutputs(proj);
sw.Stop();
tickResults[p][s] = sw.ElapsedTicks;
Console.WriteLine($"{numPredictors} @ {sparsenessPercentage}%: {sw.ElapsedTicks} ticks");
}
}
}
}
private class MockPredictor : IProjectStaticPredictor
{
private readonly StaticPredictions _predictionsToReturn;
public MockPredictor(StaticPredictions predictionsToReturn)
{
_predictionsToReturn = predictionsToReturn;
}
public bool TryPredictInputsAndOutputs(
Project project,
ProjectInstance projectInstance,
string repositoryRootDirectory,
out StaticPredictions predictions)
{
if (_predictionsToReturn == null)
{
predictions = null;
return false;
}
predictions = _predictionsToReturn;
return true;
}
}
// Second class name to get different results from PredictedBy values.
private class MockPredictor2 : MockPredictor
{
public MockPredictor2(StaticPredictions predictionsToReturn)
: base(predictionsToReturn)
{
}
}
}
}

Просмотреть файл

@ -1,19 +0,0 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
using System.Collections.Generic;
using Xunit;
namespace Microsoft.Build.Prediction.Tests
{
public class ProjectStaticPredictorFactoryTests
{
[Fact]
public void CreateStandardPredictorsSucceeds()
{
IReadOnlyCollection<IProjectStaticPredictor> predictors =
ProjectStaticPredictorFactory.CreateStandardPredictors(TestHelpers.GetAssemblyLocation());
Assert.True(predictors.Count > 0, "Reflection should have found instances of IProjectStaticPredictor in Microsoft.Build.Prediction.dll");
}
}
}

Просмотреть файл

@ -1,69 +0,0 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using Microsoft.Build.Construction;
using Microsoft.Build.Definition;
using Microsoft.Build.Evaluation;
using Microsoft.Build.Execution;
using Microsoft.Build.Prediction.StandardPredictors;
using Xunit;
namespace Microsoft.Build.Prediction.Tests.StandardPredictors
{
// TODO: Need to add .NET Core and .NET Framework based examples including use of SDK includes.
public class AvailableItemNameItemsTests
{
[SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "testContext", Justification = "Needed for reflection")]
static AvailableItemNameItemsTests()
{
MsBuildEnvironment.Setup(TestHelpers.GetAssemblyLocation());
}
[Fact]
public void AvailableItemNamesFindItems()
{
Project project = CreateTestProject(
new[] { "Available1", "Available2" },
Tuple.Create("Available1", "available1Value"),
Tuple.Create("Available1", "available1Value2"),
Tuple.Create("Available2", "available2Value"),
Tuple.Create("NotAvailable", "shouldNotGetThisAsAnInput"));
ProjectInstance projectInstance = project.CreateProjectInstance(ProjectInstanceSettings.ImmutableWithFastItemLookup);
var predictor = new AvailableItemNameItems();
bool hasPredictions = predictor.TryPredictInputsAndOutputs(project, projectInstance, @"C:\repo", out StaticPredictions predictions);
Assert.True(hasPredictions);
predictions.AssertPredictions(
new[]
{
new BuildInput(Path.Combine(project.DirectoryPath, "available1Value"), isDirectory: false),
new BuildInput(Path.Combine(project.DirectoryPath,"available1Value2"), isDirectory: false),
new BuildInput(Path.Combine(project.DirectoryPath,"available2Value"), isDirectory: false),
}, null);
}
private static Project CreateTestProject(IEnumerable<string> availableItemNames, params Tuple<string, string>[] itemNamesAndValues)
{
ProjectRootElement projectRootElement = ProjectRootElement.Create();
// Add Items.
ProjectItemGroupElement itemGroup = projectRootElement.AddItemGroup();
foreach (Tuple<string, string> itemNameAndValue in itemNamesAndValues)
{
itemGroup.AddItem(itemNameAndValue.Item1, itemNameAndValue.Item2);
}
// Add AvailableItemName items referring to the item names we'll add soon.
ProjectItemGroupElement namesItemGroup = projectRootElement.AddItemGroup();
foreach (string availableItemName in availableItemNames)
{
namesItemGroup.AddItem(AvailableItemNameItems.AvailableItemName, availableItemName);
}
return TestHelpers.CreateProjectFromRootElement(projectRootElement);
}
}
}

Просмотреть файл

@ -1,48 +0,0 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using Microsoft.Build.Construction;
using Microsoft.Build.Definition;
using Microsoft.Build.Evaluation;
using Microsoft.Build.Execution;
using Microsoft.Build.Prediction.StandardPredictors;
using Xunit;
namespace Microsoft.Build.Prediction.Tests.StandardPredictors
{
// TODO: Need to add .NET Core and .NET Framework based examples including use of SDK includes.
public class CSharpCompileItemsTests
{
[SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "testContext", Justification = "Needed for reflection")]
static CSharpCompileItemsTests()
{
MsBuildEnvironment.Setup(TestHelpers.GetAssemblyLocation());
}
[Fact]
public void CSharpFilesFoundFromDirectListingInCsproj()
{
Project project = CreateTestProject("Test.cs");
ProjectInstance projectInstance = project.CreateProjectInstance(ProjectInstanceSettings.ImmutableWithFastItemLookup);
var predictor = new CSharpCompileItems();
bool hasPredictions = predictor.TryPredictInputsAndOutputs(project, projectInstance, @"C:\repo", out StaticPredictions predictions);
Assert.True(hasPredictions);
predictions.AssertPredictions(new[] { new BuildInput(Path.Combine(project.DirectoryPath, "Test.cs"), isDirectory: false) }, null);
}
private static Project CreateTestProject(params string[] compileItemIncludes)
{
ProjectRootElement projectRootElement = ProjectRootElement.Create();
ProjectItemGroupElement itemGroup = projectRootElement.AddItemGroup();
foreach (string compileItemInclude in compileItemIncludes)
{
itemGroup.AddItem("Compile", compileItemInclude);
}
return TestHelpers.CreateProjectFromRootElement(projectRootElement);
}
}
}

Просмотреть файл

@ -1,308 +0,0 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
using System;
using System.IO;
using Microsoft.Build.Prediction.StandardPredictors.CopyTask;
using Xunit;
namespace Microsoft.Build.Prediction.Tests.StandardPredictors
{
public class CopyTaskPredictorTests : TestBase
{
internal const string CopyTestsDirectoryPath = @"TestsData\Copy\";
private readonly BuildInput _copy1Dll = new BuildInput("copy1.dll", false);
private readonly BuildInput _copy2Dll = new BuildInput("copy2.dll", false);
private readonly BuildInput _copy3Dll = new BuildInput(@"Copy\copy3.dll", false);
protected override string TestsDirectoryPath => CopyTestsDirectoryPath;
[Fact]
public void TestDefaultTargetDestinationFilesCopyProject()
{
BuildInput[] expectedInputs =
{
_copy1Dll,
_copy2Dll,
};
BuildOutputDirectory[] expectedOutputs =
{
new BuildOutputDirectory(CreateAbsolutePath(@"target\debug\amd64\folder1")),
new BuildOutputDirectory(CreateAbsolutePath(@"target\debug\amd64\folder2")),
};
var predictor = new CopyTaskPredictor();
ParseAndVerifyProject("destinationFilesCopy.csproj", predictor, expectedInputs, expectedOutputs);
}
[Fact]
public void TestCustomTargetFilesCopy()
{
BuildInput[] expectedInputs =
{
_copy1Dll,
_copy2Dll,
};
BuildOutputDirectory[] expectedOutputs =
{
new BuildOutputDirectory(CreateAbsolutePath(@"target\debug\amd64\folder1")),
new BuildOutputDirectory(CreateAbsolutePath(@"target\debug\amd64\folder2")),
};
var predictor = new CopyTaskPredictor();
ParseAndVerifyProject("customTargetWithCopy.csproj", predictor, expectedInputs, expectedOutputs);
}
/// <summary>
/// Tests copy parsing for non-standard default targets.
/// Makes sure that when DefaultTargets/InitialTargets specified differ from the default Build target,
/// we ignore the default and only consider the Build target.
/// </summary>
[Fact]
public void TestCopyParseNonStandardDefaultTargets()
{
BuildInput[] expectedInputs =
{
_copy1Dll,
};
BuildOutputDirectory[] expectedOutputs =
{
new BuildOutputDirectory(CreateAbsolutePath(@"target\debug\amd64\folder1")),
};
var predictor = new CopyTaskPredictor();
ParseAndVerifyProject("CopyDefaultCustomTargets.csproj", predictor, expectedInputs, expectedOutputs);
}
/// <summary>
/// Tests the copy inputs before after targets.
/// Scenario: With MSBuild v4.0, Target Synchronization can happen with DependsOnTargets
/// and Before/After targets on the downstream target. This ensures that inputs from those copy tasks are captured in the predictions.
/// We need to use a custom targets file since all targets in the project are automatically added to be parsed and do not test the logic
/// of target synchronizations.
/// </summary>
[Fact]
public void TestCopyInputsBeforeAfterTargets()
{
BuildInput[] expectedInputs =
{
_copy1Dll,
_copy2Dll,
};
BuildOutputDirectory[] expectedOutputs =
{
new BuildOutputDirectory(CreateAbsolutePath(@"target\debug\amd64\folder1")),
new BuildOutputDirectory(CreateAbsolutePath(@"target\debug\amd64\folder3")),
new BuildOutputDirectory(CreateAbsolutePath(@"target\debug\amd64\folder4")),
};
var predictor = new CopyTaskPredictor();
ParseAndVerifyProject("CopyCustomImportedTargets.csproj", predictor, expectedInputs, expectedOutputs);
}
/// <summary>
/// Tests the copy batched items with this file macros.
/// Scenario: Copy tasks are allowed to declare inputs with MSBuild batching. To capture more dependency
/// closure to get a more complete DGG, parsing these batched inputs is recommended. In the absence of such
/// parsing, users would have to declare QCustomInput/Outputs for each of the batched copies.
/// Additionally, copy tasks in targets that exist in other folders can use $(MSBuildThisFile) macros that evaluate
/// to something outside the Project's context. These need to be evaluated correctly as well.
/// </summary>
[Fact]
public void TestCopyBatchedItemsWithThisFileMacros()
{
BuildInput[] expectedInputs =
{
_copy1Dll,
_copy2Dll,
// TODO: Note double backslash in test - add path normalization and canonicalization to input and output paths.
new BuildInput(Path.Combine(Environment.CurrentDirectory, CopyTestsDirectoryPath) + @"Copy\\copy3.dll", false),
};
BuildOutputDirectory[] expectedOutputs =
{
new BuildOutputDirectory(CreateAbsolutePath(@"target\debug\amd64\folder1")),
new BuildOutputDirectory(CreateAbsolutePath(@"target\debug\amd64\folder2")),
new BuildOutputDirectory(CreateAbsolutePath(@"target\debug\amd64\folder3")),
};
var predictor = new CopyTaskPredictor();
ParseAndVerifyProject("CopyTestBatchingInputs.csproj", predictor, expectedInputs, expectedOutputs);
}
/// <summary>
/// Test that copy tasks with batch inputs work.
/// </summary>
[Fact]
public void TestCopyBatchingDestinationFolder()
{
BuildInput[] expectedInputs =
{
_copy1Dll,
_copy2Dll,
new BuildInput("SomeFile.cs", false),
};
BuildOutputDirectory[] expectedOutputs =
{
// TODO: Note trailing backslash in test - add path normalization and canonicalization to input and output paths.
new BuildOutputDirectory(CreateAbsolutePath(@"debug\amd64\")),
};
var predictor = new CopyTaskPredictor();
ParseAndVerifyProject("CopyTestBatchingDestinationFolder.csproj", predictor, expectedInputs, expectedOutputs);
}
[Fact]
public void TestCopyParseTimeNotExistFilesCopyProject()
{
BuildInput[] expectedInputs =
{
new BuildInput("NotExist1.dll", false),
new BuildInput("NotExist2.dll", false),
};
BuildOutputDirectory[] expectedOutputs =
{
new BuildOutputDirectory(CreateAbsolutePath(@"target\debug\amd64\folder1")),
new BuildOutputDirectory(CreateAbsolutePath(@"target\debug\amd64\folder2")),
};
var predictor = new CopyTaskPredictor();
ParseAndVerifyProject("copyparsetimenotexistfile.csproj", predictor, expectedInputs, expectedOutputs);
}
[Fact]
public void TestWildcardsInIncludeCopyProject()
{
BuildInput[] expectedInputs =
{
_copy1Dll,
_copy2Dll,
_copy3Dll,
};
BuildOutputDirectory[] expectedOutputs =
{
new BuildOutputDirectory(CreateAbsolutePath(@"target\debug\amd64\folder1")),
};
var predictor = new CopyTaskPredictor();
ParseAndVerifyProject("wildcardsInIncludeCopy.csproj", predictor, expectedInputs, expectedOutputs);
}
[Fact]
public void TestIncludeViaItemGroupCopyProject()
{
BuildInput[] expectedInputs =
{
_copy1Dll,
_copy2Dll,
};
BuildOutputDirectory[] expectedOutputs =
{
new BuildOutputDirectory(CreateAbsolutePath(@"target\debug\amd64\folder1")),
};
var predictor = new CopyTaskPredictor();
ParseAndVerifyProject("IncludeViaItemGroupCopy.csproj", predictor, expectedInputs, expectedOutputs);
}
[Fact]
public void TestDestinationFilesItemTransformationCopyProject()
{
BuildInput[] expectedInputs =
{
_copy1Dll,
_copy2Dll,
};
BuildOutputDirectory[] expectedOutputs =
{
new BuildOutputDirectory(CreateAbsolutePath(@"target\debug\amd64\folder1")),
};
var predictor = new CopyTaskPredictor();
ParseAndVerifyProject("DestinationFilesItemTransformation.csproj", predictor, expectedInputs, expectedOutputs);
}
[Fact]
public void TestMultipleTargetsDestinationFolderCopyProject()
{
BuildInput[] expectedInputs =
{
_copy1Dll,
_copy2Dll,
};
BuildOutputDirectory[] expectedOutputs =
{
new BuildOutputDirectory(CreateAbsolutePath(@"target\debug\amd64\folder1")),
new BuildOutputDirectory(CreateAbsolutePath(@"target\debug\amd64\folder2")),
};
var predictor = new CopyTaskPredictor();
ParseAndVerifyProject("destinationFolderMultipleTargetsCopy.csproj", predictor, expectedInputs, expectedOutputs);
}
[Fact]
public void TestTargetDependsOnCopyProject()
{
BuildInput[] expectedInputs =
{
_copy1Dll,
_copy2Dll,
};
BuildOutputDirectory[] expectedOutputs =
{
new BuildOutputDirectory(CreateAbsolutePath(@"target\debug\amd64\folder1")),
new BuildOutputDirectory(CreateAbsolutePath(@"target\debug\amd64\folder2")),
};
var predictor = new CopyTaskPredictor();
ParseAndVerifyProject("TargetDependsOnCopy.csproj", predictor, expectedInputs, expectedOutputs);
}
[Fact]
public void TestTargetConditionInCopyProject()
{
BuildInput[] expectedInputs =
{
_copy1Dll,
};
BuildOutputDirectory[] expectedOutputs =
{
new BuildOutputDirectory(CreateAbsolutePath(@"target\debug\amd64\folder1")),
};
var predictor = new CopyTaskPredictor();
ParseAndVerifyProject("TargetConditionInCopy.csproj", predictor, expectedInputs, expectedOutputs);
}
[Fact]
public void TestTaskConditionInCopyProject()
{
BuildInput[] expectedInputs =
{
_copy1Dll,
};
BuildOutputDirectory[] expectedOutputs =
{
new BuildOutputDirectory(CreateAbsolutePath(@"target\debug\amd64\folder1")),
};
var predictor = new CopyTaskPredictor();
ParseAndVerifyProject("TaskConditionInCopy.csproj", predictor, expectedInputs, expectedOutputs);
}
}
}

Просмотреть файл

@ -1,66 +0,0 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
using System.Collections.Generic;
using System.IO;
using Microsoft.Build.Construction;
using Microsoft.Build.Evaluation;
using Microsoft.Build.Execution;
using Microsoft.Build.Prediction.StandardPredictors;
using Xunit;
namespace Microsoft.Build.Prediction.Tests.StandardPredictors
{
public class IntermediateOutputPathIsOutputDirTests
{
static IntermediateOutputPathIsOutputDirTests()
{
MsBuildEnvironment.Setup(TestHelpers.GetAssemblyLocation());
}
[Fact]
public void IntermediateOutputPathFoundAsOutputDir()
{
const string IntermediateOutputPath = @"C:\repo\bin\x64";
Project project = CreateTestProject(IntermediateOutputPath);
ProjectInstance projectInstance = project.CreateProjectInstance(ProjectInstanceSettings.ImmutableWithFastItemLookup);
var predictor = new IntermediateOutputPathIsOutputDir();
bool hasPredictions = predictor.TryPredictInputsAndOutputs(project, projectInstance, @"C:\repo", out StaticPredictions predictions);
Assert.True(hasPredictions);
predictions.AssertPredictions(null, new[] { new BuildOutputDirectory(IntermediateOutputPath) });
}
[Fact]
public void RelativeIntermediateOutputPathFoundAsOutputDir()
{
const string IntermediateOutputPath = @"bin\x64";
Project project = CreateTestProject(IntermediateOutputPath);
ProjectInstance projectInstance = project.CreateProjectInstance(ProjectInstanceSettings.ImmutableWithFastItemLookup);
var predictor = new IntermediateOutputPathIsOutputDir();
bool hasPredictions = predictor.TryPredictInputsAndOutputs(project, projectInstance, @"C:\repo", out StaticPredictions predictions);
Assert.True(hasPredictions);
predictions.AssertPredictions(null, new[] { new BuildOutputDirectory(Path.Combine(Directory.GetCurrentDirectory(), IntermediateOutputPath)) });
}
[Fact]
public void NoOutputsReportedIfNoIntermediateOutputPath()
{
Project project = CreateTestProject(null);
ProjectInstance projectInstance = project.CreateProjectInstance(ProjectInstanceSettings.ImmutableWithFastItemLookup);
var predictor = new IntermediateOutputPathIsOutputDir();
bool hasPredictions = predictor.TryPredictInputsAndOutputs(project, projectInstance, @"C:\repo", out _);
Assert.False(hasPredictions, "Predictor should have fallen back to returning no predictions if IntermediateOutputDir is not defined in project");
}
private static Project CreateTestProject(string intermediateOutDir)
{
ProjectRootElement projectRootElement = ProjectRootElement.Create();
if (intermediateOutDir != null)
{
projectRootElement.AddProperty(IntermediateOutputPathIsOutputDir.IntermediateOutputPathMacro, intermediateOutDir);
}
return TestHelpers.CreateProjectFromRootElement(projectRootElement);
}
}
}

Просмотреть файл

@ -1,70 +0,0 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
using System.Collections.Generic;
using Microsoft.Build.Construction;
using Microsoft.Build.Evaluation;
using Microsoft.Build.Execution;
using Microsoft.Build.Prediction.StandardPredictors;
using Xunit;
namespace Microsoft.Build.Prediction.Tests.StandardPredictors
{
public class OutDirOrOutputPathIsOutputDirTests
{
static OutDirOrOutputPathIsOutputDirTests()
{
MsBuildEnvironment.Setup(TestHelpers.GetAssemblyLocation());
}
[Fact]
public void OutDirFoundAsOutputDir()
{
const string outDir = @"C:\repo\bin\x64";
Project project = CreateTestProject(outDir, null);
ProjectInstance projectInstance = project.CreateProjectInstance(ProjectInstanceSettings.ImmutableWithFastItemLookup);
var predictor = new OutDirOrOutputPathIsOutputDir();
bool hasPredictions = predictor.TryPredictInputsAndOutputs(project, projectInstance, @"C:\repo", out StaticPredictions predictions);
Assert.True(hasPredictions);
predictions.AssertPredictions(null, new[] { new BuildOutputDirectory(outDir) });
}
[Fact]
public void OutputPathUsedAsFallback()
{
const string outputPath = @"C:\repo\OutputPath";
Project project = CreateTestProject(null, outputPath);
ProjectInstance projectInstance = project.CreateProjectInstance(ProjectInstanceSettings.ImmutableWithFastItemLookup);
var predictor = new OutDirOrOutputPathIsOutputDir();
bool hasPredictions = predictor.TryPredictInputsAndOutputs(project, projectInstance, @"C:\repo", out StaticPredictions predictions);
Assert.True(hasPredictions);
predictions.AssertPredictions(null, new[] { new BuildOutputDirectory(outputPath) });
}
[Fact]
public void NoOutputsReportedIfNoOutDirOrOutputPath()
{
Project project = CreateTestProject(null, null);
ProjectInstance projectInstance = project.CreateProjectInstance(ProjectInstanceSettings.ImmutableWithFastItemLookup);
var predictor = new OutDirOrOutputPathIsOutputDir();
bool hasPredictions = predictor.TryPredictInputsAndOutputs(project, projectInstance, @"C:\repo", out _);
Assert.False(hasPredictions, "Predictor should have fallen back to returning no predictions if OutDir and OutputPath are not defined in project");
}
private static Project CreateTestProject(string outDir, string outputPath)
{
ProjectRootElement projectRootElement = ProjectRootElement.Create();
if (outDir != null)
{
projectRootElement.AddProperty(OutDirOrOutputPathIsOutputDir.OutDirMacro, outDir);
}
if (outputPath != null)
{
projectRootElement.AddProperty(OutDirOrOutputPathIsOutputDir.OutputPathMacro, outputPath);
}
return TestHelpers.CreateProjectFromRootElement(projectRootElement);
}
}
}

Просмотреть файл

@ -1,43 +0,0 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
using System;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using Microsoft.Build.Prediction;
using Microsoft.Build.Prediction.StandardPredictors;
using Microsoft.Build.Prediction.Tests;
using Xunit;
namespace BuildPredictionTests.StandardPredictors
{
public class ProjectFileAndImportedFilesTests : TestBase
{
internal const string ImportTestsDirectoryPath = @"TestsData\Import";
internal const string NestedImportsProjectFileName = "NestedImports.csproj";
[SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "testContext", Justification = "Needed for reflection")]
static ProjectFileAndImportedFilesTests()
{
MsBuildEnvironment.Setup(TestHelpers.GetAssemblyLocation());
}
protected override string TestsDirectoryPath => ImportTestsDirectoryPath;
[Fact]
public void ProjectFileAndNestedImportedFilesInCsProj()
{
BuildInput[] expectedInputs =
{
new BuildInput(Path.Combine(Environment.CurrentDirectory, ImportTestsDirectoryPath, NestedImportsProjectFileName), false),
new BuildInput(Path.Combine(Environment.CurrentDirectory, ImportTestsDirectoryPath, @"Import\NestedTargets.targets"), false),
new BuildInput(Path.Combine(Environment.CurrentDirectory, ImportTestsDirectoryPath, @"Import\NestedTargets2.targets"), false)
};
BuildOutputDirectory[] expectedOutputs = null;
var predictor = new ProjectFileAndImportedFiles();
ParseAndVerifyProject(NestedImportsProjectFileName, predictor, expectedInputs, expectedOutputs);
}
}
}

Просмотреть файл

@ -1,74 +0,0 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Microsoft.Build.Evaluation;
using Microsoft.Build.Execution;
using Xunit;
namespace Microsoft.Build.Prediction.Tests
{
/// <summary>
/// Base class that provides helper methods for test code, including
/// interfacing with sample files in the TestsData folder.
/// </summary>
public abstract class TestBase
{
static TestBase()
{
MsBuildEnvironment.Setup(s_assemblyLocation);
}
private static string s_assemblyLocation = TestHelpers.GetAssemblyLocation();
/// <summary>
/// Gets the relative path for resource files used by the test suite.
/// The path is relative to the BuildPredictionTests output folder.
/// This is typically something like @"TestsData\Xxx" where Xxx is the
/// test suite type.
/// </summary>
protected abstract string TestsDirectoryPath { get; }
/// <summary>
/// Creates an absolute path using the assembly location as the root, followed by <see cref="TestsDirectoryPath"/>
/// </summary>
protected string CreateAbsolutePath(string relativePath)
{
return Path.Combine(s_assemblyLocation, TestsDirectoryPath, relativePath);
}
protected void ParseAndVerifyProject(
string projFileName,
IProjectStaticPredictor predictor,
IReadOnlyCollection<BuildInput> expectedInputs,
IReadOnlyCollection<BuildOutputDirectory> expectedOutputs)
{
var projectCollection = new ProjectCollection();
var project = new Project(
Path.Combine(TestsDirectoryPath, projFileName), // TestsData files are marked to CopyToOutput and are available next to the executing assembly
new Dictionary<string, string>
{
{ "Platform", "amd64" },
{ "Configuration", "debug" },
},
null,
projectCollection);
ProjectInstance projectInstance = project.CreateProjectInstance(ProjectInstanceSettings.ImmutableWithFastItemLookup);
bool success = predictor.TryPredictInputsAndOutputs(
project,
projectInstance,
TestsDirectoryPath,
out StaticPredictions predictions);
IReadOnlyCollection<BuildInput> absolutePathInputs = expectedInputs.Select(i =>
new BuildInput(Path.Combine(project.DirectoryPath, i.Path), i.IsDirectory))
.ToList();
Assert.True(success, "Prediction returned false (no predictions)");
predictions.AssertPredictions(absolutePathInputs, expectedOutputs);
}
}
}

Просмотреть файл

@ -1,103 +0,0 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Xml;
using Microsoft.Build.Construction;
using Microsoft.Build.Definition;
using Microsoft.Build.Evaluation;
using Xunit;
namespace Microsoft.Build.Prediction.Tests
{
internal static class TestHelpers
{
public static void AssertPredictions(
this StaticPredictions predictions,
IReadOnlyCollection<BuildInput> expectedBuildInputs,
IReadOnlyCollection<BuildOutputDirectory> expectedBuildOutputDirectories)
{
Assert.NotNull(predictions);
if (expectedBuildInputs == null)
{
Assert.Equal(0, predictions.BuildInputs.Count);
}
else
{
CheckCollection(expectedBuildInputs, predictions.BuildInputs, BuildInput.ComparerInstance, "inputs");
}
if (expectedBuildOutputDirectories == null)
{
Assert.Equal(0, predictions.BuildOutputDirectories.Count);
}
else
{
CheckCollection(expectedBuildOutputDirectories, predictions.BuildOutputDirectories, BuildOutputDirectory.ComparerInstance, "outputs");
}
}
public static Project ProjectFromXml(string xml)
{
var settings = new XmlReaderSettings
{
XmlResolver = null, // Prevent external calls for namespaces.
};
using (var stringReader = new StringReader(xml))
using (var xmlReader = XmlReader.Create(stringReader, settings))
{
ProjectRootElement projectRootElement = ProjectRootElement.Create(xmlReader);
return Project.FromProjectRootElement(projectRootElement, new ProjectOptions());
}
}
public static Project CreateProjectFromRootElement(ProjectRootElement projectRootElement)
{
var globalProperties = new Dictionary<string, string>
{
{ "Platform", "amd64" },
{ "Configuration", "debug" },
};
// TODO: Remove the hardcoded toolsVersion.
// We are hardcoding the toolVersion value since the Microsoft.Build version that we are currently using
// throws when calling Project.FromProjectRootElement(projectRootElement, new ProjectOptions());
return new Project(projectRootElement, globalProperties, toolsVersion: "4.0");
}
private static void CheckCollection<T>(IReadOnlyCollection<T> expected, IReadOnlyCollection<T> actual, IEqualityComparer<T> comparer, string type)
{
var actualSet = new HashSet<T>(actual, comparer);
var expectedSet = new HashSet<T>(expected, comparer);
List<T> expectedNotInActual = expected.Where(i => !actualSet.Contains(i)).ToList();
List<T> actualNotExpected = actual.Where(i => !expectedSet.Contains(i)).ToList();
if (expectedSet.Count != actualSet.Count)
{
throw new ArgumentException(
$"Mismatched count - expected {expectedSet.Count} but got {actualSet.Count}. \r\n" +
$"Expected {type} [[{string.Join(Environment.NewLine, expected)}]] \r\n" +
$"Actual [[{string.Join(Environment.NewLine, actual)}]] \r\n" +
$"Extra expected [[{string.Join(Environment.NewLine, expectedNotInActual)}]] \r\n" +
$"Extra actual [[{string.Join(Environment.NewLine, actualNotExpected)}]]");
}
foreach (T expectedItem in expectedSet)
{
Assert.True(
actualSet.Contains(expectedItem),
$"Missed value in the {type}: {expectedItem} from among actual list {string.Join(":: ", actual)}");
}
}
public static string GetAssemblyLocation()
{
return Path.GetDirectoryName(new Uri(Assembly.GetExecutingAssembly().CodeBase).LocalPath);
}
}
}

Просмотреть файл

@ -1,8 +0,0 @@
<?xml version="1.0" encoding="utf-8" ?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Target Name="Copy3sCopyTarget" BeforeTargets="Build">
<Copy SourceFiles="$(MsbuildthisFILEDirectory)\copy3.dll"
DestinationFolder="target\$(Configuration)\$(Platform)\$(SubFolderForCopy3)" />
</Target>
<Target Name="Build" />
</Project>

Просмотреть файл

@ -1 +0,0 @@


Просмотреть файл

@ -1,16 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<ProjectGuid>{0000000A-0000-00AA-AA00-0AA00A00A00A}</ProjectGuid>
<OutputType>Exe</OutputType>
<OutDir>objd\amd64</OutDir>
<RootNamespace>SomeNamespace</RootNamespace>
<AssemblyName>SomeName</AssemblyName>
</PropertyGroup>
<ItemGroup>
<Compile Include="SomeFile.cs" />
<FilesToCopy Include="copy1.dll" />
<CustomFilesToCopy Include="copy2.dll" />
</ItemGroup>
<Import Project="CustomCopyBeforeAfterImports.targets"/>
</Project>

Просмотреть файл

@ -1,20 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" DefaultTargets="DefaultCustomTarget" InitialTargets="InitialCustomTarget" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<ProjectGuid>{0000000A-0000-00AA-AA00-0AA00A00A00A}</ProjectGuid>
<OutputType>Exe</OutputType>
<OutDir>objd\amd64</OutDir>
<RootNamespace>SomeNamespace</RootNamespace>
<AssemblyName>SomeName</AssemblyName>
</PropertyGroup>
<ItemGroup>
<Compile Include="SomeFile.cs" />
<FilesToCopy Include="copy1.dll" />
<CustomFilesToCopy Include="copy2.dll" />
</ItemGroup>
<ItemGroup>
<DestFiles Include="target\$(Configuration)\$(Platform)\folder1\copy1.dll" />
<CustomDestFiles Include="target\$(Configuration)\$(Platform)\folder1\copy2.dll" />
</ItemGroup>
<Import Project="CustomCopyImports.targets"/>
</Project>

Просмотреть файл

@ -1,42 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" DefaultTargets="GoodBatch;BadBatch1;BadBatch2" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<ProjectGuid>{0000000A-0000-00AA-AA00-0AA00A00A00A}</ProjectGuid>
<OutputType>Exe</OutputType>
<OutDir>$(Configuration)\$(Platform)</OutDir>
<RootNamespace>SomeNamespace</RootNamespace>
<AssemblyName>SomeName</AssemblyName>
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
</ItemGroup>
<ItemGroup>
<Compile Include="SomeFile.cs" />
</ItemGroup>
<ItemGroup>
<FilesToCopy Include="copy1.dll" />
<FilesToCopy Include="copy2.dll" />
</ItemGroup>
<ItemGroup>
<DestFiles Include="private\folder\target\copy1.dll" />
<DestFiles Include="public\folder2\debug\amd64\copy2.dll" />
</ItemGroup>
<Target Name="GoodBatchSingleDir">
<Copy SourceFiles="%(FilesToCopy.Identity)" DestinationFolder="$(OutDir)" />
</Target>
<Target Name="GoodBatchMultipleDirs">
<Copy SourceFiles="%(FilesToCopy.Identity)" DestinationFolder="$(OutDir)\%(FilesToCopy.RelativeDir)" />
</Target>
<Target Name="MixBatchWithLiteral">
<Copy SourceFiles="%(FilesToCopy.Identity);copy1.dll" DestinationFolder="$(OutDir)\%(FilesToCopy.RelativeDir)" />
</Target>
<Target Name="MixBatchWithItemGroup">
<Copy SourceFiles="@(FilesToCopy);%(FilesToCopy.Identity)" DestinationFolder="$(OutDir)\%(FilesToCopy.RelativeDir)" />
</Target>
<Target Name="MultipleBatches">
<Copy SourceFiles="%(Compile.Identity);%(FilesToCopy.Identity)" DestinationFolder="$(OutDir)\%(FilesToCopy.RelativeDir)" />
</Target>
<Target Name="ItemCountMismatch">
<Copy SourceFiles="@(FilesToCopy);@(Compile)" DestinationFolder="$(OutDir)\dir1;$(OutDir)\dir2" />
</Target>
</Project>

Просмотреть файл

@ -1,31 +0,0 @@
<?xml version="1.0" encoding="utf-8" ?>
<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<ProjectGuid>{0000000A-0000-00AA-AA00-0AA00A00A00A}</ProjectGuid>
<OutputType>Exe</OutputType>
<OutDir>objd\amd64</OutDir>
<RootNamespace>SomeNamespace</RootNamespace>
<AssemblyName>SomeName</AssemblyName>
</PropertyGroup>
<ItemGroup>
<Compile Include="SomeFile.cs" />
<FilesToCopy Include="copy1.dll">
<CustomFileFolderName>folder1</CustomFileFolderName>
</FilesToCopy>
<FilesToCopy Include="copy2.dll">
<CustomFileFolderName>folder2</CustomFileFolderName>
</FilesToCopy>
</ItemGroup>
<Target Name="MyCopyTarget">
<Copy SourceFiles="%(FilesToCopy.Identity)"
DestinationFiles="target\$(Configuration)\$(Platform)\%(FilesToCopy.CustomFileFolderName)\%(FilesToCopy.FileName).dll" />
</Target>
<Import Project="Copy\SubFolderCopyTargets.targets"/>
<!--
Evaluation of the copy3 item should be done at the bottom of the proj file, so SubFolderForCopy3 should be available as a property
Even though the ThisFile is evaluated in the context of the imported targets file.
-->
<PropertyGroup>
<SubFolderForCopy3>folder3</SubFolderForCopy3>
</PropertyGroup>
</Project>

Просмотреть файл

@ -1,25 +0,0 @@
<?xml version="1.0" encoding="utf-8" ?>
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<!-- Create a scenario where these targets run based on already hooked targets.-->
<Target Name="CustomBeforeCopyTarget" BeforeTargets="Build">
<Copy SourceFiles="@(FilesToCopy)" DestinationFiles="@(FilesToCopy-> 'target\$(Configuration)\$(Platform)\folder1\%(FileName)%(Extension)')" />
</Target>
<Target Name="CustomAfterCopyTarget" AfterTargets="Build">
<Copy SourceFiles="@(CustomFilesToCopy)" DestinationFiles="@(CustomFilesToCopy-> 'target\$(Configuration)\$(Platform)\folder1\%(FileName)%(Extension)')" />
</Target>
<Target Name="Build" />
<!-- Add targets that are hooked onto non-existent targets and will not execute. -->
<Target Name="CustomBeforeNonExistentCopyTarget" BeforeTargets="DefaultNonExistentTarget">
<Copy SourceFiles="@(FilesToCopy)" DestinationFiles="@(FilesToCopy-> 'target\$(Configuration)\$(Platform)\folder2\%(FileName)%(Extension)')" />
</Target>
<Target Name="DefaultNonExistentTarget" />
<!-- Add targets that are compounded i.e. run Before a target scheduled by a BeforeTarget -->
<Target Name="TargetBeforeCustomCopyTarget" BeforeTargets="CustomBeforeCopyTarget">
<Copy SourceFiles="@(CustomFilesToCopy)" DestinationFiles="@(CustomFilesToCopy-> 'target\$(Configuration)\$(Platform)\folder3\%(FileName)%(Extension)')" />
</Target>
<Target Name="MultiLevelTargetBeforeCustomCopyTarget" AfterTargets="TargetBeforeCustomCopyTarget">
<Copy SourceFiles="@(FilesToCopy)" DestinationFiles="@(FilesToCopy-> 'target\$(Configuration)\$(Platform)\folder4\%(FileName)%(Extension)')" />
</Target>
</Project>

Просмотреть файл

@ -1,13 +0,0 @@
<?xml version="1.0" encoding="utf-8" ?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Target Name="CustomTarget">
<Copy SourceFiles="@(FilesToCopy)" DestinationFiles="@(DestFiles)" />
</Target>
<Target Name="Build" DependsOnTargets="CustomTarget" />
<Target Name="DefaultCustomTarget" DependsOnTargets="CustomCopyTargetNotParsed" />
<Target Name="CustomCopyTargetNotParsed">
<Copy SourceFiles="@(CustomFilesToCopy)" DestinationFiles="@(CustomDestFiles)" />
</Target>
</Project>

Просмотреть файл

@ -1,26 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" DefaultTargets="CopyFiles1" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<ProjectGuid>{0000000A-0000-00AA-AA00-0AA00A00A00A}</ProjectGuid>
<OutputType>Exe</OutputType>
<OutDir>objd\amd64</OutDir>
<RootNamespace>SomeNamespace</RootNamespace>
<AssemblyName>SomeName</AssemblyName>
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
</ItemGroup>
<ItemGroup>
<Compile Include="SomeFile.cs" />
</ItemGroup>
<ItemGroup>
<FilesToCopy Include="copy1.dll" />
<FilesToCopy Include="copy2.dll" />
</ItemGroup>
<PropertyGroup>
<Dest>target\debug\amd64\folder1</Dest>
</PropertyGroup>
<Target Name="CopyFiles1">
<Copy SourceFiles="@(FilesToCopy)" DestinationFiles="@(FilesToCopy->'$(Dest)\new_%(Filename)%(Extension)')" />
</Target>
</Project>

Просмотреть файл

@ -1,30 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" DefaultTargets="CopyFiles1" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<ProjectGuid>{0000000A-0000-00AA-AA00-0AA00A00A00A}</ProjectGuid>
<OutputType>Exe</OutputType>
<OutDir>objd\amd64</OutDir>
<RootNamespace>SomeNamespace</RootNamespace>
<AssemblyName>SomeName</AssemblyName>
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
</ItemGroup>
<ItemGroup>
<Compile Include="SomeFile.cs" />
</ItemGroup>
<PropertyGroup>
<Dest>target\debug\amd64\folder1</Dest>
</PropertyGroup>
<ItemGroup>
<FilesToCopy Include="copy1.dll" />
<FilesToCopy Include="copy2.dll" />
</ItemGroup>
<Target Name="CopyFiles1">
<Copy SourceFiles="@(FilesToCopy)" DestinationFolder="$(Dest)" />
</Target>
</Project>

Просмотреть файл

@ -1,35 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" DefaultTargets="CopyFiles1;CopyFiles2" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<ProjectGuid>{0000000A-0000-00AA-AA00-0AA00A00A00A}</ProjectGuid>
<OutputType>Exe</OutputType>
<OutDir>objd\amd64</OutDir>
<RootNamespace>SomeNamespace</RootNamespace>
<AssemblyName>SomeName</AssemblyName>
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
</ItemGroup>
<ItemGroup>
<Compile Include="SomeFile.cs" />
</ItemGroup>
<ItemGroup>
<Copy1 Include="copy1.dll"/>
</ItemGroup>
<PropertyGroup>
<Dest1>target\debug\amd64\folder1</Dest1>
<Dest2>target\debug\amd64\folder2</Dest2>
</PropertyGroup>
<ItemGroup>
<Copy2 Include="copy2.dll"/>
</ItemGroup>
<PropertyGroup>
<TargetCondition>Copy1</TargetCondition>
</PropertyGroup>
<Target Name="CopyFiles1" Condition="'$(TargetCondition)'=='Copy1'">
<Copy SourceFiles="@(Copy1)" DestinationFolder="$(Dest1)" />
</Target>
<Target Name="CopyFiles2" Condition="'$(TargetCondition)'=='Copy2'">
<Copy SourceFiles="@(Copy2)" DestinationFolder="$(Dest2)" />
</Target>
</Project>

Просмотреть файл

@ -1,35 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" DefaultTargets="CopyFiles2" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<ProjectGuid>{0000000A-0000-00AA-AA00-0AA00A00A00A}</ProjectGuid>
<OutputType>Exe</OutputType>
<OutDir>objd\amd64</OutDir>
<RootNamespace>SomeNamespace</RootNamespace>
<AssemblyName>SomeName</AssemblyName>
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
</ItemGroup>
<ItemGroup>
<Compile Include="SomeFile.cs" />
</ItemGroup>
<ItemGroup>
<Copy1 Include="copy1.dll"/>
</ItemGroup>
<PropertyGroup>
<Dest1>target\debug\amd64\folder1</Dest1>
<Dest2>target\debug\amd64\folder2</Dest2>
</PropertyGroup>
<ItemGroup>
<Copy2 Include="copy2.dll"/>
</ItemGroup>
<ItemGroup>
<CopyDependsOn Include="CopyFiles1"/>
</ItemGroup>
<Target Name="CopyFiles1">
<Copy SourceFiles="@(Copy1)" DestinationFolder="$(Dest1)" />
</Target>
<Target Name="CopyFiles2" DependsOnTargets="@(CopyDependsOn)">
<Copy SourceFiles="@(Copy2)" DestinationFolder="$(Dest2)" />
</Target>
</Project>

Просмотреть файл

@ -1,33 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" DefaultTargets="CopyFiles1" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<ProjectGuid>{0000000A-0000-00AA-AA00-0AA00A00A00A}</ProjectGuid>
<OutputType>Exe</OutputType>
<OutDir>objd\amd64</OutDir>
<RootNamespace>SomeNamespace</RootNamespace>
<AssemblyName>SomeName</AssemblyName>
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
</ItemGroup>
<ItemGroup>
<Compile Include="SomeFile.cs" />
</ItemGroup>
<ItemGroup>
<Copy1 Include="copy1.dll"/>
</ItemGroup>
<PropertyGroup>
<Dest1>target\debug\amd64\folder1</Dest1>
<Dest2>target\debug\amd64\folder2</Dest2>
</PropertyGroup>
<ItemGroup>
<Copy2 Include="copy2.dll"/>
</ItemGroup>
<PropertyGroup>
<TaskCondition>Copy1</TaskCondition>
</PropertyGroup>
<Target Name="CopyFiles1">
<Copy SourceFiles="@(Copy1)" DestinationFolder="$(Dest1)" Condition="'$(TaskCondition)'=='Copy1'"/>
<Copy SourceFiles="@(Copy2)" DestinationFolder="$(Dest2)" Condition="'$(TaskCondition)'=='Copy2'"/>
</Target>
</Project>

Просмотреть файл

@ -1 +0,0 @@


Просмотреть файл

@ -1 +0,0 @@


Просмотреть файл

@ -1,27 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" DefaultTargets="CopyFiles1" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<ProjectGuid>{0000000A-0000-00AA-AA00-0AA00A00A00A}</ProjectGuid>
<OutputType>Exe</OutputType>
<OutDir>objd\amd64</OutDir>
<RootNamespace>SomeNamespace</RootNamespace>
<AssemblyName>SomeName</AssemblyName>
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
</ItemGroup>
<ItemGroup>
<Compile Include="SomeFile.cs" />
</ItemGroup>
<ItemGroup>
<FilesToCopy Include="notexist1.dll" />
<FilesToCopy Include="notexist2.dll" />
</ItemGroup>
<ItemGroup>
<DestFiles Include="target\$(Configuration)\$(Platform)\folder1\notexist1.dll" />
<DestFiles Include="target\$(Configuration)\$(Platform)\folder2\notexist2.dll" />
</ItemGroup>
<Target Name="CopyFiles1">
<Copy SourceFiles="@(FilesToCopy)" DestinationFiles="@(DestFiles)" />
</Target>
</Project>

Просмотреть файл

@ -1,27 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<ProjectGuid>{0000000A-0000-00AA-AA00-0AA00A00A00A}</ProjectGuid>
<OutputType>Exe</OutputType>
<OutDir>objd\amd64</OutDir>
<RootNamespace>SomeNamespace</RootNamespace>
<AssemblyName>SomeName</AssemblyName>
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
</ItemGroup>
<ItemGroup>
<Compile Include="SomeFile.cs" />
</ItemGroup>
<ItemGroup>
<FilesToCopy Include="copy1.dll" />
<FilesToCopy Include="copy2.dll" />
</ItemGroup>
<ItemGroup>
<DestFiles Include="target\$(Configuration)\$(Platform)\folder1\copy1.dll" />
<DestFiles Include="target\$(Configuration)\$(Platform)\folder2\copy2.dll" />
</ItemGroup>
<Target Name="CustomTarget">
<Copy SourceFiles="@(FilesToCopy)" DestinationFiles="@(DestFiles)" />
</Target>
</Project>

Просмотреть файл

@ -1,27 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" DefaultTargets="CopyFiles1" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<ProjectGuid>{0000000A-0000-00AA-AA00-0AA00A00A00A}</ProjectGuid>
<OutputType>Exe</OutputType>
<OutDir>objd\amd64</OutDir>
<RootNamespace>SomeNamespace</RootNamespace>
<AssemblyName>SomeName</AssemblyName>
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
</ItemGroup>
<ItemGroup>
<Compile Include="SomeFile.cs" />
</ItemGroup>
<ItemGroup>
<FilesToCopy Include="copy1.dll" />
<FilesToCopy Include="copy2.dll" />
</ItemGroup>
<ItemGroup>
<DestFiles Include="target\$(Configuration)\$(Platform)\folder1\copy1.dll" />
<DestFiles Include="target\$(Configuration)\$(Platform)\folder2\copy2.dll" />
</ItemGroup>
<Target Name="CopyFiles1">
<Copy SourceFiles="@(FilesToCopy)" DestinationFiles="@(DestFiles)" />
</Target>
</Project>

Просмотреть файл

@ -1,32 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" DefaultTargets="CopyFiles1;CopyFiles2" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<ProjectGuid>{0000000A-0000-00AA-AA00-0AA00A00A00A}</ProjectGuid>
<OutputType>Exe</OutputType>
<OutDir>objd\amd64</OutDir>
<RootNamespace>SomeNamespace</RootNamespace>
<AssemblyName>SomeName</AssemblyName>
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
</ItemGroup>
<ItemGroup>
<Compile Include="SomeFile.cs" />
</ItemGroup>
<ItemGroup>
<Copy1 Include="copy1.dll"/>
</ItemGroup>
<PropertyGroup>
<Dest1>target\$(Configuration)\$(Platform)\folder1</Dest1>
<Dest2>target\$(Configuration)\$(Platform)\folder2</Dest2>
</PropertyGroup>
<ItemGroup>
<Copy2 Include="copy2.dll"/>
</ItemGroup>
<Target Name="CopyFiles1">
<Copy SourceFiles="@(Copy1)" DestinationFolder="$(Dest1)" />
</Target>
<Target Name="CopyFiles2">
<Copy SourceFiles="@(Copy2)" DestinationFolder="$(Dest2)" />
</Target>
</Project>

Просмотреть файл

@ -1,27 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" DefaultTargets="CopyFiles1" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<ProjectGuid>{0000000A-0000-00AA-AA00-0AA00A00A00A}</ProjectGuid>
<OutputType>Exe</OutputType>
<OutDir>objd\amd64</OutDir>
<RootNamespace>SomeNamespace</RootNamespace>
<AssemblyName>SomeName</AssemblyName>
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
</ItemGroup>
<ItemGroup>
<Compile Include="SomeFile.cs" />
</ItemGroup>
<ItemGroup>
<Copy1 Include="**\*.dll"/>
</ItemGroup>
<PropertyGroup>
<Dest>target\debug\amd64\folder1</Dest>
</PropertyGroup>
<Target Name="CopyFiles1">
<Copy SourceFiles="@(Copy1)" DestinationFolder="$(Dest)" />
</Target>
</Project>

Просмотреть файл

@ -1,6 +0,0 @@
<?xml version="1.0" encoding="utf-8" ?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Target Name="Build" />
<Import Project="NestedTargets2.targets"/>
</Project>

Просмотреть файл

@ -1,4 +0,0 @@
<?xml version="1.0" encoding="utf-8" ?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Target Name="Build" />
</Project>

Просмотреть файл

@ -1,14 +0,0 @@
<?xml version="1.0" encoding="utf-8" ?>
<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<ProjectGuid>{0000000A-0000-00AA-AA00-0AA00A00A00A}</ProjectGuid>
<OutputType>Exe</OutputType>
<OutDir>objd\amd64</OutDir>
<RootNamespace>SomeNamespace</RootNamespace>
<AssemblyName>SomeName</AssemblyName>
</PropertyGroup>
<ItemGroup>
<Compile Include="SomeFile.cs" />
</ItemGroup>
<Import Project="Import\NestedTargets.targets"/>
</Project>

61
Public/External/BuildPrediction/Design.md поставляемый
Просмотреть файл

@ -1,61 +0,0 @@
# Microsoft.Build.Prediction Design Notes
BuildPrediction is loosely based on portions of a Microsoft-internal build engine that wraps MSBuild and provides build output caching and multi-machine distribution on top of [CloudBuild](https://www.microsoft.com/en-us/research/publication/cloudbuild-microsofts-distributed-and-caching-build-service/) to speed up builds especially for mid-to-large codebases. A particular component, EnlistmentLibrary or ENL for short, reads MSBuild files and outputs a dependency graph based on both ProjectReferences and predicting file and directory inputs and outputs and cross-comparing them to find dependencies. The file and directory inputs and outputs are then fed into caching algorithms to try to find cached build outputs.
Notably, because the inputs and outputs are predicted from the XML and object model of MSBuild, custom Targets can perform opaque tasks that are not easily parsed by ENL, and newer Microsoft and third-party SDKs can add new build logic that ENL does not understand without dev work to handle the newer patterns, even a high fidelity and bug-free implementation will have prediction holes. This can and does lead to missing links in the dependency graph, requiring downstream logic to watch the actual actions of executing processes and breaking the build on accesses to the outputs of unknown dependencies. Also, because MSBuild itself did not have a native concept of ProjectReferences, and did not perform this input:output matching, executing under the Microsoft internal build engine could produce different results from running MSBuild.exe or Visual Studio locally.
With the advent of the [MSBuild Static Graph](https://github.com/Microsoft/msbuild/blob/master/documentation/specs/static-graph.md), MSBuild is taking a large step toward making proj files single-purpose, and making it possible to determine the dependency graph quickly by making ProjectReferences be the dependency graph. Pivotally, similar tracking and bookkeeping logic similar to the internal build engine's implementation is needed for Graph API to enforce that missing ProjectReferences are a build break.
BuildPrediction is designed to work with the Graph API to make it possible to provide build caching for MSBuild.
## Requirements
### Top-level Requirements
* BuildPrediction is assumed to be downstream from the MSBuild Graph API and can take a dependency on part or all of the resulting graph object model.
* BuildPrediction uses the Project as the unit of execution and caching. This matches the Graph API semantics, and matches the internal Microsoft build engine semantics. Note that this is in opposition to more complicated build models where Project files' Targets take cross-dependencies with the Targets of other Projects in a back-and-forth pattern, e.g. build "passes;" the Graph API model labels these as circular dependencies and requires that such Project graphs be unwound into a larger DAG.
* BuildPrediction must produce a set of source code file input predictions for each Project in the graph. These predictions are intended to be - but are not required to be - as close as possible to the actual list of source files read, to ensure that an initial hash of the contents of these inputs produces a different hash-of-hashes to make lookups of cached outputs more exact. However, given the current nature of caching technology, underspecified source inputs produce more inefficient, but not broken, cache lookups, and overspecification is considered an anti-dependency when the predicted files are missing. The anti-dependency becomes a dependency on the file if in the next cache check the file appeared in the filesystem, and the hash of the newly appears file feeds into the cache lookup.
* BuildPrediction must produce a set of build output directories/folders per Project where build outputs are placed. This information is fed into build execution logic to provide build output sandboxing, including output directory virtualization. Over-prediction here is not a problem.
* BuildPrediction must use all available processor cores to get its work done as fast as possible in wall clock time terms. Practically, since MSBuild Static Graph has already parsed the Project files into an object model before Prediction is invoked, generating predictions should be a CPU-intensive task, not I/O intensive. In datacenters there tend to be a very high number of cores, and most dev machines have several. Let's not keep devs waiting any longer than we have to.
* BuildPrediction must define a minimum version of MSBuild that it makes use of, but must use the ambient MSBuild provided by the user's appdomain environment to make use of the latest bug fixes and features in that version of MSBuild.
* MSBuild APIs used by BuildPrediction must be public; the MSBuild team makes a lot of effort to provide downlevel API compatibility, at least within a single major version number.
* BuildPrediction initially targets .NET Framework for compatibility with Visual Studio and the Framework flavor of MSBuild. It may be updated to provide a .NET Standard flavor depending on the need of downstream consumers, e.g. use in a .Net Core build engine on Linux and Mac.
* BuildPrediction must be shipped to users as a NuGet package, using standard SemVer versioning.
* BuildPrediction implements a set of "standard" predictors maintained within the BuildPrediciton repo and shipped in the package, but may provide a pluggable model for externally written Predictors to be added.
* BuildPrediction may provide a configuration mechanism to disable individual Predictors based on operational needs of a specific build. For example, a Predictor that is producing bad results in a specific repo might be completely turned off, or turned off in favor of a custom replacement Predictor that produces a better result for that repo.
* BuildPrediction must be executed within the scope of a single repo encompassing a specific filesystem subdirectory called the RepositoryRoot and all subdirectories below that. When a build that includes Git submodules is in use, the RepositoryRoot is typically the subdirectory containing the outermost Git repo.
### Requirements for Predictors
Predictors are implementations of logic to read a parsed Project and, possibly, the MSBuild Static Graph, and without executing any logic within the Project or changing the filesystem, produce a set of input and output predictions.
* A Predictor exists in an ecosystem of other Predictors that run at the same time.
* The scope of a predictor is defined by its implementer. It can read properties, items, targets, and other entities in the MSBuild object model and produce zero or more individual file and directory/folder predictions. It can produce a single predicted file or directory item, or it can produce a vast quantity of predictions of varying types from any or all properties of a Project. However, we encourage Predictors to be focused in their purpose: Transforming, say, a single Project property into one, a few, or no predictions allows easier unit testing and makes it easier to turn off the Predictor to snip out its limited functionality.
* Predictor instances are created and destroyed within the context of a single parsing session, and can assume that the filesystem will not change during its lifetime. A parsing session would typically manifest as parsing the current repository contents to run a single build. This can allow optimizations arising from caching filesystem contents during parsing.
* Predictors must act as pure, idempotent functions and are effective singletons: They are provided inputs in the form of configuration, a Project, and possibly the MSBuild Static Graph, and their prediction logic must be able to execute with many threads executing the prediction logic on different Projects at the same time on different threads. They must be idempotent: If executed twice on the same Project they must produce the same results. They should not store any state, except where otherwise noted.
* Each Predictor must have unit tests that cover its logic at a 90% or higher level, including exception cases. See the unit test suite in this repo for examples and approaches.
## Implementation Notes
As you would expect, any difference between this spec, which started aging poorly the moment it was written, and the actual code should use the code as reality. Patch this spec in a PR if you find notable differences.
### Predictors
Project-focused Predictors derive from [IProjectStaticPredictor](BuildPrediction/IProjectStaticPredictor.cs).
### Object Model for Predictions
See [StaticPredictions](BuildPrediction/StaticPredictions.cs) for the object produced for Project-level inputs and outputs. [ProjectStaticPredictionExecutor](BuildPrediction/ProjectStaticPredictionExecutor.cs) orchestrates parallel execution of Predictors.
### RepositoryRoot Directory and Absolute Paths
The source code for a repo is assumed to exist within a filesystem subdirectory, encompassed by the RepositoryRoot property. Build input paths are normalized to be relative to this RepositoryRoot except where the inputs are specified to be absolute paths and those paths lie outside the RepositoryRoot. Build output directories are allowed to exist outside the RepositoryRoot but must be absolute paths in this case.
Absolute paths are detected on Windows using a fully-qualified drive letter and backslash, and by a leading / for Mac and Linux.
### Unresolved Spec Issues
* Determine approach for PathTable trie usage and how it interacts with paths.
## Q&A
**Q:** Why does BuildPrediction not need to generate output file predictions, just directories/folders?
**A:** ENL, from the internal Microsoft build engine, needs to produce all inputs and outputs for cross-matching to produce a more stable and true dependency graph. With MSBuild Static Graph, the dependency graph is wholly dependent on ProjectReferences, cutting the need for this time-consuming cross-matching step. However, build execution sandboxing does need information about where the Project will be writing its outputs.
**Q:** Why not provide Target-level or Exec-level caching? Why is a Project the top-level concept?
**A:** Project-level execution and caching has proved to work well for the internal Microsoft build engine since 2011. Targets are rather under-specified even if their Inputs and Outputs collections are well written, and many Targets tend to rely on unspecified ordering and side effects in the filesystem and in-memory object model. Project-level caching has caused some onboarding pain for teams that had created complex "build passes" that created Target-level round-tripping amongst Projects, which had to be unwound into a larger DAG of single-purpose Projects, but the result has been to provide build caching on developer and datacenter machines, and multi-machine builds in the datacenter, moving single-machine builds from multiple hours down to seconds or minutes in common high cache hit rate cases, and even on cache misses providing much faster results than single-machine builds. Graph API and BuildPrediction build on that base of experience.
**Q:** This spec and the Graph API spec talk about caching and distribution, which the [CloudBuild whitepaper](https://www.microsoft.com/en-us/research/publication/cloudbuild-microsofts-distributed-and-caching-build-service/) discusses as a Microsoft internal system, but I don't see it anywhere in the public Microsoft GitHub repos. Why?
**A:** Have patience. This is but a stepping-stone to cool stuff.

24
Public/External/BuildPrediction/LICENSE поставляемый
Просмотреть файл

@ -1,24 +0,0 @@
Microsoft.Build.Prediction
The MIT License (MIT)
Copyright (c) .NET Foundation and contributors
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

18
Public/External/BuildPrediction/README.md поставляемый
Просмотреть файл

@ -1,18 +0,0 @@
# Microsoft.Build.Prediction
NOTE: Forked from an MS-internal repo 'BuildPrediction' as part of public release of this codebase. This work may be moved into a separate GitHub repo. Text below was adapted for this context.
This direcotory generates the Microsoft.Build.Prediction assembly, a library containing predictors that run against evaluated MSBuild [Project]([https://docs.microsoft.com/en-us/dotnet/api/microsoft.build.evaluation.project?view=netframework-4.7.2]) instances to predict file and directory inputs that will be read, and output directories that will be written, by the project.
Predictors are implementations of the IProjectStaticPredictor interface. Execution logic in this library applies the predictors in parallel to a given Project. The library aggregates results from all predictors into a final set of predicted inputs and outputs for a Project.
Input and output predictions produced here can be used, for instance, for Project build caching and sandboxing. Predicted inputs are added to the project file itself and known global files and folders from SDKs and tools to produce a set of files and folders that can be hashed and summarized to produce an inputs hash that can be used to look up the results of previous build executions. The more accurate and complete the predicted set of inputs, the narrower the set of cached output results, and the better the cache performance. Predicted build output directories are used to guide static construction and analysis of build sandboxes.
## Usage
### Custom Plug-in Assemblies
## Design
See [Design](Design.md).
## License
Microsoft.Build.Prediction is licensed under the [MIT License](https://github.com/Microsoft/msbuild/blob/master/LICENSE).

Просмотреть файл

@ -1,9 +0,0 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
module({
// TODO: this name is clashing with MsBuild module (called "Microsoft.Build" as well). And because of the order among resolvers
// this module is not being built under BuildXL!
name: "Microsoft.Build.Prediction",
nameResolutionSemantics: NameResolutionSemantics.implicitProjectReferences
});

Просмотреть файл

@ -22,9 +22,6 @@ export const msbuildReferences: Managed.ManagedNugetPackage[] = [
importFrom("Microsoft.Build.Tasks.Core").pkg, importFrom("Microsoft.Build.Tasks.Core").pkg,
]; ];
@@public
export const msbuildLocatorReferences: Managed.ManagedNugetPackage[] = [importFrom("Microsoft.Build.Locator").pkg];
@@public @@public
export const msbuildRuntimeContent = [ export const msbuildRuntimeContent = [
importFrom("System.Numerics.Vectors").pkg, importFrom("System.Numerics.Vectors").pkg,

Просмотреть файл

@ -39,7 +39,7 @@ namespace MsBuildGraphBuilderTool
// See https://github.com/Microsoft/msbuild/blob/master/documentation/specs/static-graph.md#inferring-which-targets-to-run-for-a-project-within-the-graph // See https://github.com/Microsoft/msbuild/blob/master/documentation/specs/static-graph.md#inferring-which-targets-to-run-for-a-project-within-the-graph
// TODO: maybe the static graph API can provide this information in the future in a more native way // TODO: maybe the static graph API can provide this information in the future in a more native way
private const string ProjectReferenceTargets = "ProjectReferenceTargets"; private const string ProjectReferenceTargets = "ProjectReferenceTargets";
/// <summary> /// <summary>
/// Makes sure the required MsBuild assemblies are loaded from, uses the MsBuild static graph API to get a build graph starting /// Makes sure the required MsBuild assemblies are loaded from, uses the MsBuild static graph API to get a build graph starting
/// at the project entry point and serializes it to an output file. /// at the project entry point and serializes it to an output file.
@ -64,7 +64,7 @@ namespace MsBuildGraphBuilderTool
/// For tests only. Similar to <see cref="BuildGraphAndSerialize(MSBuildGraphBuilderArguments)"/>, but the assembly loader and reporter can be passed explicitly /// For tests only. Similar to <see cref="BuildGraphAndSerialize(MSBuildGraphBuilderArguments)"/>, but the assembly loader and reporter can be passed explicitly
/// </summary> /// </summary>
internal static void BuildGraphAndSerializeForTesting( internal static void BuildGraphAndSerializeForTesting(
IMsBuildAssemblyLoader assemblyLoader, IMsBuildAssemblyLoader assemblyLoader,
GraphBuilderReporter reporter, GraphBuilderReporter reporter,
MSBuildGraphBuilderArguments arguments, MSBuildGraphBuilderArguments arguments,
IReadOnlyCollection<IProjectStaticPredictor> projectPredictorsForTesting = null) IReadOnlyCollection<IProjectStaticPredictor> projectPredictorsForTesting = null)
@ -77,7 +77,7 @@ namespace MsBuildGraphBuilderTool
} }
private static void DoBuildGraphAndSerialize( private static void DoBuildGraphAndSerialize(
IMsBuildAssemblyLoader assemblyLoader, IMsBuildAssemblyLoader assemblyLoader,
GraphBuilderReporter reporter, GraphBuilderReporter reporter,
MSBuildGraphBuilderArguments arguments, MSBuildGraphBuilderArguments arguments,
IReadOnlyCollection<IProjectStaticPredictor> projectPredictorsForTesting = null) IReadOnlyCollection<IProjectStaticPredictor> projectPredictorsForTesting = null)
@ -134,10 +134,10 @@ namespace MsBuildGraphBuilderTool
var projectInstanceToProjectCache = new ConcurrentDictionary<ProjectInstance, Project>(); var projectInstanceToProjectCache = new ConcurrentDictionary<ProjectInstance, Project>();
if (!TryBuildEntryPoints( if (!TryBuildEntryPoints(
graphBuildArguments.ProjectsToParse, graphBuildArguments.ProjectsToParse,
graphBuildArguments.RequestedQualifiers, graphBuildArguments.RequestedQualifiers,
graphBuildArguments.GlobalProperties, graphBuildArguments.GlobalProperties,
out List<ProjectGraphEntryPoint> entryPoints, out List<ProjectGraphEntryPoint> entryPoints,
out string failure)) out string failure))
{ {
return ProjectGraphWithPredictionsResult.CreateFailure( return ProjectGraphWithPredictionsResult.CreateFailure(
@ -149,7 +149,7 @@ namespace MsBuildGraphBuilderTool
var projectGraph = new ProjectGraph( var projectGraph = new ProjectGraph(
entryPoints, entryPoints,
// The project collection doesn't need any specific global properties, since entry points already contain all the ones that are needed, and the project graph will merge them // The project collection doesn't need any specific global properties, since entry points already contain all the ones that are needed, and the project graph will merge them
new ProjectCollection(), new ProjectCollection(),
(projectPath, globalProps, projectCollection) => ProjectInstanceFactory(projectPath, globalProps, projectCollection, projectInstanceToProjectCache)); (projectPath, globalProps, projectCollection) => ProjectInstanceFactory(projectPath, globalProps, projectCollection, projectInstanceToProjectCache));
// This is a defensive check to make sure the assembly loader actually honored the search locations provided by the user. The path of the assembly where ProjectGraph // This is a defensive check to make sure the assembly loader actually honored the search locations provided by the user. The path of the assembly where ProjectGraph
@ -161,28 +161,27 @@ namespace MsBuildGraphBuilderTool
if (!assemblyPathsToLoad.Values.Contains(assemblylocation, StringComparer.InvariantCultureIgnoreCase)) if (!assemblyPathsToLoad.Values.Contains(assemblylocation, StringComparer.InvariantCultureIgnoreCase))
{ {
return ProjectGraphWithPredictionsResult.CreateFailure( return ProjectGraphWithPredictionsResult.CreateFailure(
GraphConstructionError.CreateFailureWithoutLocation($"Internal error: the assembly '{assembly.GetName().Name}' was loaded from '{assemblylocation}'. This path doesn't match any of the provided search locations. Please contact the BuildXL team."), GraphConstructionError.CreateFailureWithoutLocation($"Internal error: the assembly '{assembly.GetName().Name}' was loaded from '{assemblylocation}'. This path doesn't match any of the provided search locations. Please contact the BuildXL team."),
assemblyPathsToLoad, assemblyPathsToLoad,
locatedMsBuildPath); locatedMsBuildPath);
} }
reporter.ReportMessage("Done parsing MSBuild specs."); reporter.ReportMessage("Done parsing MSBuild specs.");
if (!TryConstructGraph( if (!TryConstructGraph(
projectGraph, projectGraph,
locatedMsBuildPath, graphBuildArguments.EnlistmentRoot,
graphBuildArguments.EnlistmentRoot, reporter,
reporter,
projectInstanceToProjectCache, projectInstanceToProjectCache,
graphBuildArguments.EntryPointTargets, graphBuildArguments.EntryPointTargets,
projectPredictorsForTesting, projectPredictorsForTesting,
graphBuildArguments.AllowProjectsWithoutTargetProtocol, graphBuildArguments.AllowProjectsWithoutTargetProtocol,
out ProjectGraphWithPredictions projectGraphWithPredictions, out ProjectGraphWithPredictions projectGraphWithPredictions,
out failure)) out failure))
{ {
return ProjectGraphWithPredictionsResult.CreateFailure( return ProjectGraphWithPredictionsResult.CreateFailure(
GraphConstructionError.CreateFailureWithoutLocation(failure), GraphConstructionError.CreateFailureWithoutLocation(failure),
assemblyPathsToLoad, assemblyPathsToLoad,
locatedMsBuildPath); locatedMsBuildPath);
} }
@ -218,9 +217,9 @@ namespace MsBuildGraphBuilderTool
/// configuration) plus the particular qualifier, which is passed to MSBuild as properties as well. /// configuration) plus the particular qualifier, which is passed to MSBuild as properties as well.
/// </remarks> /// </remarks>
private static bool TryBuildEntryPoints( private static bool TryBuildEntryPoints(
IReadOnlyCollection<string> projectsToParse, IReadOnlyCollection<string> projectsToParse,
IReadOnlyCollection<GlobalProperties> requestedQualifiers, IReadOnlyCollection<GlobalProperties> requestedQualifiers,
GlobalProperties globalProperties, GlobalProperties globalProperties,
out List<ProjectGraphEntryPoint> entryPoints, out List<ProjectGraphEntryPoint> entryPoints,
out string failure) out string failure)
{ {
@ -299,10 +298,9 @@ namespace MsBuildGraphBuilderTool
} }
private static bool TryConstructGraph( private static bool TryConstructGraph(
ProjectGraph projectGraph, ProjectGraph projectGraph,
string locatedMsBuildPath, string enlistmentRoot,
string enlistmentRoot, GraphBuilderReporter reporter,
GraphBuilderReporter reporter,
ConcurrentDictionary<ProjectInstance, Project> projectInstanceToProjectCache, ConcurrentDictionary<ProjectInstance, Project> projectInstanceToProjectCache,
IReadOnlyCollection<string> entryPointTargets, IReadOnlyCollection<string> entryPointTargets,
IReadOnlyCollection<IProjectStaticPredictor> projectPredictorsForTesting, IReadOnlyCollection<IProjectStaticPredictor> projectPredictorsForTesting,
@ -311,7 +309,6 @@ namespace MsBuildGraphBuilderTool
out string failure) out string failure)
{ {
Contract.Assert(projectGraph != null); Contract.Assert(projectGraph != null);
Contract.Assert(!string.IsNullOrEmpty(locatedMsBuildPath));
var projectNodes = new ProjectWithPredictions[projectGraph.ProjectNodes.Count]; var projectNodes = new ProjectWithPredictions[projectGraph.ProjectNodes.Count];
@ -335,7 +332,7 @@ namespace MsBuildGraphBuilderTool
IReadOnlyCollection<IProjectStaticPredictor> predictors; IReadOnlyCollection<IProjectStaticPredictor> predictors;
try try
{ {
predictors = projectPredictorsForTesting ?? ProjectStaticPredictorFactory.CreateStandardPredictors(locatedMsBuildPath); predictors = projectPredictorsForTesting ?? ProjectStaticPredictors.BasicPredictors;
} }
catch(Exception ex) catch(Exception ex)
{ {
@ -372,9 +369,9 @@ namespace MsBuildGraphBuilderTool
catch(Exception ex) catch(Exception ex)
{ {
predictedIOFailures.Enqueue(( predictedIOFailures.Enqueue((
new string[] { "Unknown predictor" }, new string[] { "Unknown predictor" },
$"Cannot run static predictor on project '{project.FullPath ?? "Unknown project"}'. An unexpected error occurred. Please contact BuildPrediction project owners with this stack trace: {ex.ToString()}")); $"Cannot run static predictor on project '{project.FullPath ?? "Unknown project"}'. An unexpected error occurred. Please contact BuildPrediction project owners with this stack trace: {ex.ToString()}"));
// Stick an empty prediction. The error will be caught anyway after all predictors are done. // Stick an empty prediction. The error will be caught anyway after all predictors are done.
predictions = new StaticPredictions(new BuildInput[] { }, new BuildOutputDirectory[] { }); predictions = new StaticPredictions(new BuildInput[] { }, new BuildOutputDirectory[] { });
} }
@ -391,10 +388,10 @@ namespace MsBuildGraphBuilderTool
} }
if (!TryGetPredictedTargetsAndPropertiesToExecute( if (!TryGetPredictedTargetsAndPropertiesToExecute(
projectInstance, projectInstance,
msBuildNode, msBuildNode,
targetsPerProject, targetsPerProject,
computedTargets, computedTargets,
pendingAddDefaultTargets, pendingAddDefaultTargets,
allowProjectsWithoutTargetProtocol, allowProjectsWithoutTargetProtocol,
out GlobalProperties globalProperties, out GlobalProperties globalProperties,
@ -424,7 +421,7 @@ namespace MsBuildGraphBuilderTool
msBuildNodesToNodeWithPredictionIndex[msBuildNode] = projectNodes[i]; msBuildNodesToNodeWithPredictionIndex[msBuildNode] = projectNodes[i];
}); });
// There were IO prediction errors. // There were IO prediction errors.
if (!predictedIOFailures.IsEmpty) if (!predictedIOFailures.IsEmpty)
{ {
projectGraphWithPredictions = new ProjectGraphWithPredictions(new ProjectWithPredictions<string>[] { }); projectGraphWithPredictions = new ProjectGraphWithPredictions(new ProjectWithPredictions<string>[] { });
@ -433,7 +430,7 @@ namespace MsBuildGraphBuilderTool
return false; return false;
} }
// There were target prediction errors. // There were target prediction errors.
if (!predictedTargetFailures.IsEmpty) if (!predictedTargetFailures.IsEmpty)
{ {
projectGraphWithPredictions = new ProjectGraphWithPredictions(new ProjectWithPredictions<string>[] { }); projectGraphWithPredictions = new ProjectGraphWithPredictions(new ProjectWithPredictions<string>[] { });
@ -519,7 +516,7 @@ namespace MsBuildGraphBuilderTool
// This is the case where the project doesn't implement the protocol, it has non-empty references // This is the case where the project doesn't implement the protocol, it has non-empty references
// and projects without a protocol are not allowed. // and projects without a protocol are not allowed.
failure = $"Project '{projectInstance.FullPath}' is not specifying its project reference protocol. For more details see https://github.com/Microsoft/msbuild/blob/master/documentation/specs/static-graph.md"; failure = $"Project '{projectInstance.FullPath}' is not specifying its project reference protocol. For more details see https://github.com/Microsoft/msbuild/blob/master/documentation/specs/static-graph.md";
computedTargets = null; computedTargets = null;
globalPropertiesForNode = GlobalProperties.Empty; globalPropertiesForNode = GlobalProperties.Empty;

Просмотреть файл

@ -24,7 +24,7 @@ namespace MsBuildGraphBuilder {
importFrom("BuildXL.Utilities.Instrumentation").Common.dll, importFrom("BuildXL.Utilities.Instrumentation").Common.dll,
importFrom("System.Collections.Immutable").pkg, importFrom("System.Collections.Immutable").pkg,
importFrom("DataflowForMSBuildRuntime").pkg, importFrom("DataflowForMSBuildRuntime").pkg,
importFrom("Microsoft.Build.Prediction").BuildPrediction.dll, importFrom("Microsoft.Build.Prediction").pkg,
NetFx.System.Threading.Tasks.dll, NetFx.System.Threading.Tasks.dll,
...MSBuild.msbuildReferences, ...MSBuild.msbuildReferences,
], ],

Просмотреть файл

@ -9,7 +9,7 @@ namespace Test.Tool.MsBuildGraphBuilder {
export declare const qualifier: MSBuild.MSBuildQualifier; export declare const qualifier: MSBuild.MSBuildQualifier;
// If the current qualifier is full framework, this tool has to be built with 472 // If the current qualifier is full framework, this tool has to be built with 472
const msBuildGraphBuilderReference : Managed.Assembly = const msBuildGraphBuilderReference : Managed.Assembly =
importFrom("BuildXL.Tools").MsBuildGraphBuilder.withQualifier(to472(qualifier)).exe; importFrom("BuildXL.Tools").MsBuildGraphBuilder.withQualifier(to472(qualifier)).exe;
@@public @@public
@ -17,10 +17,10 @@ namespace Test.Tool.MsBuildGraphBuilder {
assemblyName: "Test.Tool.ProjectGraphBuilder", assemblyName: "Test.Tool.ProjectGraphBuilder",
sources: globR(d`.`, "*.cs"), sources: globR(d`.`, "*.cs"),
// TODO: QTest // TODO: QTest
testFramework: importFrom("Sdk.Managed.Testing.XUnit").framework, testFramework: importFrom("Sdk.Managed.Testing.XUnit").framework,
references:[ references:[
msBuildGraphBuilderReference, msBuildGraphBuilderReference,
importFrom("Microsoft.Build.Prediction").BuildPrediction.dll, importFrom("Microsoft.Build.Prediction").pkg,
importFrom("Newtonsoft.Json").pkg, importFrom("Newtonsoft.Json").pkg,
importFrom("BuildXL.FrontEnd").MsBuild.Serialization.dll, importFrom("BuildXL.FrontEnd").MsBuild.Serialization.dll,
...MSBuild.msbuildReferences, ...MSBuild.msbuildReferences,

Просмотреть файл

@ -13,7 +13,6 @@ config({
...globR(d`Private/QTest`, "module.config.dsc"), ...globR(d`Private/QTest`, "module.config.dsc"),
...globR(d`Private/InternalSdk`, "module.config.dsc"), ...globR(d`Private/InternalSdk`, "module.config.dsc"),
...globR(d`Private/Tools`, "module.config.dsc"), ...globR(d`Private/Tools`, "module.config.dsc"),
...globR(d`Public/External/BuildPrediction`, "module.config.dsc"),
...globR(d`Public/Sdk/SelfHost`, "module.config.dsc"), ...globR(d`Public/Sdk/SelfHost`, "module.config.dsc"),
], ],
@ -444,7 +443,9 @@ config({
// Extra dependencies to make MSBuild work // Extra dependencies to make MSBuild work
{ id: "Microsoft.VisualStudio.Setup.Configuration.Interop", version: "1.16.30"}, { id: "Microsoft.VisualStudio.Setup.Configuration.Interop", version: "1.16.30"},
{ id: "System.CodeDom", version: "4.4.0"}, { id: "System.CodeDom", version: "4.4.0"},
{ id: "Microsoft.Build.Locator", version: "1.0.31"},
// Used for MSBuild input/output prediction
{ id: "Microsoft.Build.Prediction", version: "0.1.0" },
{ id: "SharpZipLib", version: "1.1.0" }, { id: "SharpZipLib", version: "1.1.0" },