diff --git a/documentation/specs/proposed/partial-evaluation.md b/documentation/specs/proposed/partial-evaluation.md new file mode 100644 index 00000000000..3db0e1d53c6 --- /dev/null +++ b/documentation/specs/proposed/partial-evaluation.md @@ -0,0 +1,111 @@ +# Partial (stop-after-pass) project evaluation + +Tracking issue: [dotnet/msbuild#14288](https://github.com/dotnet/msbuild/issues/14288) +Related SDK consumer: [dotnet/sdk#55193](https://github.com/dotnet/sdk/issues/55193) + +## Motivation + +Some callers only need data produced by an early evaluation pass. The most common example is +reading a single property (for example the SDK's `ReleasePropertyProjectLocator` reads +`PublishRelease`/`PackRelease`), which today forces a **full** evaluation of the project — all +passes, including item globbing, using-task registration, and target registration. + +MSBuild evaluation runs a fixed sequence of passes: + +| Pass | Work | +| ---- | ---- | +| 0 | Initial properties (environment, global, toolset, reserved) | +| 1 | Properties + imports (also gathers item/item-definition/using-task/target *elements* and `InitialTargets`) | +| 2 | Item definitions | +| 3 / 3.1 | Items (includes wildcard/glob expansion) | +| 4 | Using-tasks (task registry) | +| 5 | Targets (registration, `DefaultTargets`, before/after maps) | + +Property values are final after pass 1: properties cannot depend on items (item references inside +property values expand to empty even in a full evaluation), so a stop-at-properties evaluation +produces property values identical to a full evaluation. + +On a file-heavy project (500 source files, ~200 properties, 50 targets), stopping after the +properties pass measured roughly a **46%** reduction in per-evaluation wall-clock versus a full +evaluation (Debug engine build; relative comparison). Passes 2–5 dominate the remainder, with item +globbing being the largest single contributor as source-file count grows. + +## API + +A new opt-in knob on `ProjectOptions` selects how far evaluation proceeds: + +```csharp +namespace Microsoft.Build.Evaluation +{ + public enum ProjectEvaluationStage + { + Properties, // stop after pass 1 + ItemDefinitions, // stop after pass 2 + Items, // stop after pass 3 / 3.1 + UsingTasks, // stop after pass 4 + Full = int.MaxValue // default: run every pass (pass 5) + } +} +``` + +```csharp +public class ProjectOptions +{ + public ProjectEvaluationStage EvaluationStage { get; set; } = ProjectEvaluationStage.Full; +} +``` + +The stage flows through the existing factory methods: + +- `ProjectInstance.FromFile(path, options)` / `ProjectInstance.FromProjectRootElement(xml, options)` +- `Project.FromFile(path, options)` / `Project.FromProjectRootElement(xml, options)` / `Project.FromXmlReader(reader, options)` + +Both `Project.EvaluationStage` and `ProjectInstance.EvaluationStage` report the stage the object was +evaluated to. + +Example: + +```csharp +var options = new ProjectOptions +{ + EvaluationStage = ProjectEvaluationStage.Properties, + EvaluationContext = sharedContext, // complements partial evaluation; see sdk#55193 +}; + +ProjectInstance instance = ProjectInstance.FromFile(projectPath, options); +string value = instance.GetPropertyValue("PublishRelease"); // fast: only passes 0-1 ran +``` + +## Behavior of a partially-evaluated object + +- **Properties are always valid** for any stage ≥ `Properties` (`GetProperty`, `GetPropertyValue`, + `Properties`, `GlobalProperties`). `InitialTargets` is also available from `Properties` onward + because it is computed during pass 1. +- **Reading not-yet-computed state fails fast.** Members that expose state from a later pass throw + `InvalidOperationException` naming the member and the stage the object reached. Guarded members + include `ItemDefinitions` (available from `ItemDefinitions` onward), `Items`, `GetItems`, + `ItemsIgnoringCondition`, `AllEvaluatedItems`, `Targets`, and `DefaultTargets` (and their + `ProjectInstance` equivalents). +- **A partial `ProjectInstance` cannot be built.** Constructing a `BuildRequestData` from a partial + instance throws `InvalidOperationException`; a build requires a full evaluation. + +The default (`Full`) is unchanged, so existing callers are unaffected. + +## Evaluation caching + +`ProjectCollection` caches loaded `Project`s keyed on (path, global properties, tools version) — the +evaluation stage is not part of the key. To avoid serving stale partial state: + +- A cached project satisfies a request only if `cachedStage >= requestedStage`. +- `ProjectCollection.LoadProject` requests `Full`. If the only cached project for a key was + partially evaluated, it is **upgraded in place** (re-evaluated to `Full`) and returned, rather than + returning partial state or creating a duplicate cache entry. +- Calling the public `Project.ReevaluateIfNecessary()` on a partial project upgrades it to `Full` + (a partial evaluation leaves the project non-dirty, so the re-evaluation is forced). + +## Relationship to `EvaluationContext` + +Partial evaluation and a shared `EvaluationContext` are complementary levers. A shared context +caches file-system probes and SDK resolution across projects; partial evaluation skips whole passes +within each project. Batch scenarios that read one property from many projects (such as the SDK +release-property locator) benefit from using both together. diff --git a/src/Build.UnitTests/Evaluation/PartialEvaluation_Tests.cs b/src/Build.UnitTests/Evaluation/PartialEvaluation_Tests.cs new file mode 100644 index 00000000000..eaf428ea218 --- /dev/null +++ b/src/Build.UnitTests/Evaluation/PartialEvaluation_Tests.cs @@ -0,0 +1,227 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.IO; +using System.Xml; + +using Microsoft.Build.Construction; +using Microsoft.Build.Definition; +using Microsoft.Build.Evaluation; +using Microsoft.Build.Execution; +using Shouldly; +using Xunit; + +namespace Microsoft.Build.UnitTests.Evaluation +{ + /// + /// Tests for the opt-in partial (stop-after-pass) evaluation model exposed via + /// . + /// + public class PartialEvaluation_Tests : IDisposable + { + private readonly ITestOutputHelper _output; + private readonly TestEnvironment _env; + private readonly ProjectCollection _collection = new ProjectCollection(); + + public PartialEvaluation_Tests(ITestOutputHelper output) + { + _output = output; + _env = TestEnvironment.Create(_output); + } + + public void Dispose() + { + _collection.Dispose(); + _env.Dispose(); + } + + private const string ProjectXml = """ + + + Debug + $(Config)-x + @(Compile) + + + + source + + + + + + + + + + + """; + + private static ProjectRootElement CreateRootElement() + { + using XmlReader reader = XmlReader.Create(new StringReader(ProjectXml)); + return ProjectRootElement.Create(reader); + } + + private ProjectOptions OptionsFor(ProjectEvaluationStage stage) => new ProjectOptions + { + EvaluationStage = stage, + ProjectCollection = _collection, + }; + + [Fact] + public void DefaultProjectOptionsStageIsFull() + { + new ProjectOptions().EvaluationStage.ShouldBe(ProjectEvaluationStage.Full); + } + + [Theory] + [InlineData(0)] + [InlineData(5)] + [InlineData(-1)] + [InlineData(int.MaxValue - 1)] + public void EvaluationStage_RejectsUndefinedValues(int value) + { + Should.Throw(() => new ProjectOptions { EvaluationStage = (ProjectEvaluationStage)value }); + } + + [Fact] + public void PropertiesStage_ExposesPropertiesButNotItemsOrTargets_ProjectInstance() + { + ProjectInstance instance = ProjectInstance.FromProjectRootElement(CreateRootElement(), OptionsFor(ProjectEvaluationStage.Properties)); + + instance.EvaluationStage.ShouldBe(ProjectEvaluationStage.Properties); + instance.GetPropertyValue("Derived").ShouldBe("Debug-x"); + + // InitialTargets are computed during pass 1 and remain available. + Should.NotThrow(() => instance.InitialTargets); + + Should.Throw(() => instance.Items); + Should.Throw(() => instance.ItemDefinitions); + Should.Throw(() => instance.GetItems("Compile")); + Should.Throw(() => instance.Targets); + Should.Throw(() => instance.DefaultTargets); + } + + [Fact] + public void PropertiesStage_ExposesPropertiesButNotItemsOrTargets_Project() + { + Project project = Project.FromProjectRootElement(CreateRootElement(), OptionsFor(ProjectEvaluationStage.Properties)); + + project.EvaluationStage.ShouldBe(ProjectEvaluationStage.Properties); + project.GetPropertyValue("Derived").ShouldBe("Debug-x"); + + Should.Throw(() => project.Items); + Should.Throw(() => project.ItemDefinitions); + Should.Throw(() => project.GetItems("Compile")); + Should.Throw(() => project.AllEvaluatedItems); + Should.Throw(() => project.Targets); + } + + [Fact] + public void PropertyValuesEqualFullEvaluation() + { + ProjectInstance partial = ProjectInstance.FromProjectRootElement(CreateRootElement(), OptionsFor(ProjectEvaluationStage.Properties)); + ProjectInstance full = ProjectInstance.FromProjectRootElement(CreateRootElement(), OptionsFor(ProjectEvaluationStage.Full)); + + partial.GetPropertyValue("Derived").ShouldBe(full.GetPropertyValue("Derived")); + + // Item references in property values expand to empty even in a full evaluation because + // properties are evaluated before items, so both stages agree. + partial.GetPropertyValue("FromItem").ShouldBe(full.GetPropertyValue("FromItem")); + } + + [Fact] + public void ItemDefinitionsStage_ExposesItemDefinitionsButNotItemsOrTargets() + { + ProjectInstance instance = ProjectInstance.FromProjectRootElement(CreateRootElement(), OptionsFor(ProjectEvaluationStage.ItemDefinitions)); + + instance.EvaluationStage.ShouldBe(ProjectEvaluationStage.ItemDefinitions); + Should.NotThrow(() => instance.ItemDefinitions); + + Should.Throw(() => instance.Items); + Should.Throw(() => instance.Targets); + } + + [Fact] + public void ItemsStage_ExposesItemsButNotTargets() + { + ProjectInstance instance = ProjectInstance.FromProjectRootElement(CreateRootElement(), OptionsFor(ProjectEvaluationStage.Items)); + + instance.EvaluationStage.ShouldBe(ProjectEvaluationStage.Items); + instance.GetItems("Compile").Count.ShouldBe(2); + + Should.Throw(() => instance.Targets); + } + + [Fact] + public void FullStage_IsDefault_AndExposesEverything() + { + ProjectInstance instance = ProjectInstance.FromProjectRootElement(CreateRootElement(), new ProjectOptions { ProjectCollection = _collection }); + + instance.EvaluationStage.ShouldBe(ProjectEvaluationStage.Full); + instance.GetItems("Compile").Count.ShouldBe(2); + instance.Targets.ShouldContainKey("Build"); + } + + [Fact] + public void ReevaluateUpgradesPartialProjectToFull() + { + Project project = Project.FromProjectRootElement(CreateRootElement(), OptionsFor(ProjectEvaluationStage.Properties)); + + project.EvaluationStage.ShouldBe(ProjectEvaluationStage.Properties); + Should.Throw(() => project.Targets); + + project.ReevaluateIfNecessary(); + + project.EvaluationStage.ShouldBe(ProjectEvaluationStage.Full); + project.Targets.ShouldContainKey("Build"); + project.GetItems("Compile").Count.ShouldBe(2); + } + + [Fact] + public void CreateProjectInstanceFromPartialProjectUpgradesToFull() + { + Project project = Project.FromProjectRootElement(CreateRootElement(), OptionsFor(ProjectEvaluationStage.Properties)); + project.EvaluationStage.ShouldBe(ProjectEvaluationStage.Properties); + + ProjectInstance instance = project.CreateProjectInstance(); + + instance.EvaluationStage.ShouldBe(ProjectEvaluationStage.Full); + instance.Targets.ShouldContainKey("Build"); + project.EvaluationStage.ShouldBe(ProjectEvaluationStage.Full); + } + + [Fact] + public void PartialProjectInstanceCannotBeBuilt() + { + ProjectInstance instance = ProjectInstance.FromProjectRootElement(CreateRootElement(), OptionsFor(ProjectEvaluationStage.Properties)); + + Should.Throw(() => new BuildRequestData(instance, new[] { "Build" })); + } + + [Fact] + public void CacheDoesNotServePartialProjectForFullLoad() + { + TransientTestFile file = _env.CreateFile("test.proj", ProjectXml); + + Project partial = Project.FromFile(file.Path, new ProjectOptions + { + EvaluationStage = ProjectEvaluationStage.Properties, + ProjectCollection = _collection, + }); + partial.EvaluationStage.ShouldBe(ProjectEvaluationStage.Properties); + + // A subsequent full LoadProject on the same key must return a fully-evaluated project + // (the cached partial one is upgraded in place), never partial state. + Project full = _collection.LoadProject(file.Path); + full.EvaluationStage.ShouldBe(ProjectEvaluationStage.Full); + full.Targets.ShouldContainKey("Build"); + + // The partial and full references point at the same upgraded cached project. + ReferenceEquals(partial, full).ShouldBeTrue(); + partial.EvaluationStage.ShouldBe(ProjectEvaluationStage.Full); + } + } +} diff --git a/src/Build/BackEnd/BuildManager/BuildRequestData.cs b/src/Build/BackEnd/BuildManager/BuildRequestData.cs index 784196c1dca..58579cd4882 100644 --- a/src/Build/BackEnd/BuildManager/BuildRequestData.cs +++ b/src/Build/BackEnd/BuildManager/BuildRequestData.cs @@ -60,6 +60,11 @@ public BuildRequestData(ProjectInstance projectInstance, string[] targetsToBuild { ArgumentNullException.ThrowIfNull(projectInstance); + if (projectInstance.EvaluationStage != Evaluation.ProjectEvaluationStage.Full) + { + Shared.ErrorUtilities.ThrowInvalidOperation("OM_PartialEvaluationCannotBuild", projectInstance.EvaluationStage); + } + foreach (string targetName in targetsToBuild) { ArgumentNullException.ThrowIfNull(targetName, "target"); diff --git a/src/Build/Definition/Project.cs b/src/Build/Definition/Project.cs index 951f3377354..3665dfced9e 100644 --- a/src/Build/Definition/Project.cs +++ b/src/Build/Definition/Project.cs @@ -268,7 +268,7 @@ public Project(ProjectRootElement xml, IDictionary globalPropert } private Project(ProjectRootElement xml, IDictionary globalProperties, string toolsVersion, string subToolsetVersion, ProjectCollection projectCollection, ProjectLoadSettings loadSettings, - EvaluationContext evaluationContext, IDirectoryCacheFactory directoryCacheFactory, bool interactive) + EvaluationContext evaluationContext, IDirectoryCacheFactory directoryCacheFactory, bool interactive, ProjectEvaluationStage evaluationStage = ProjectEvaluationStage.Full) { ArgumentNullException.ThrowIfNull(xml); ErrorUtilities.VerifyThrowArgumentLengthIfNotNull(toolsVersion, nameof(toolsVersion)); @@ -279,7 +279,7 @@ private Project(ProjectRootElement xml, IDictionary globalProper implementation = defaultImplementation; _directoryCacheFactory = directoryCacheFactory; - defaultImplementation.Initialize(globalProperties, toolsVersion, subToolsetVersion, loadSettings, evaluationContext, interactive); + defaultImplementation.Initialize(globalProperties, toolsVersion, subToolsetVersion, loadSettings, evaluationContext, interactive, evaluationStage); } /// @@ -362,7 +362,7 @@ public Project(XmlReader xmlReader, IDictionary globalProperties } private Project(XmlReader xmlReader, IDictionary globalProperties, string toolsVersion, string subToolsetVersion, ProjectCollection projectCollection, ProjectLoadSettings loadSettings, - EvaluationContext evaluationContext, IDirectoryCacheFactory directoryCacheFactory, bool interactive) + EvaluationContext evaluationContext, IDirectoryCacheFactory directoryCacheFactory, bool interactive, ProjectEvaluationStage evaluationStage = ProjectEvaluationStage.Full) { ArgumentNullException.ThrowIfNull(xmlReader); ErrorUtilities.VerifyThrowArgumentLengthIfNotNull(toolsVersion, nameof(toolsVersion)); @@ -373,7 +373,7 @@ private Project(XmlReader xmlReader, IDictionary globalPropertie implementation = defaultImplementation; _directoryCacheFactory = directoryCacheFactory; - defaultImplementation.Initialize(globalProperties, toolsVersion, subToolsetVersion, loadSettings, evaluationContext, interactive); + defaultImplementation.Initialize(globalProperties, toolsVersion, subToolsetVersion, loadSettings, evaluationContext, interactive, evaluationStage); } /// @@ -458,7 +458,7 @@ public Project(string projectFile, IDictionary globalProperties, } private Project(string projectFile, IDictionary globalProperties, string toolsVersion, string subToolsetVersion, ProjectCollection projectCollection, ProjectLoadSettings loadSettings, - EvaluationContext evaluationContext, IDirectoryCacheFactory directoryCacheFactory, bool interactive) + EvaluationContext evaluationContext, IDirectoryCacheFactory directoryCacheFactory, bool interactive, ProjectEvaluationStage evaluationStage = ProjectEvaluationStage.Full) { ArgumentNullException.ThrowIfNull(projectFile); ErrorUtilities.VerifyThrowArgumentLengthIfNotNull(toolsVersion, nameof(toolsVersion)); @@ -475,7 +475,7 @@ private Project(string projectFile, IDictionary globalProperties // seems the XmlReader based one should also clean the same way. try { - defaultImplementation.Initialize(globalProperties, toolsVersion, subToolsetVersion, loadSettings, evaluationContext, interactive); + defaultImplementation.Initialize(globalProperties, toolsVersion, subToolsetVersion, loadSettings, evaluationContext, interactive, evaluationStage); } catch (Exception ex) when (!ExceptionHandling.IsCriticalException(ex)) { @@ -507,7 +507,8 @@ public static Project FromFile(string file, ProjectOptions options) options.LoadSettings, options.EvaluationContext, options.DirectoryCacheFactory, - options.Interactive); + options.Interactive, + options.EvaluationStage); } /// @@ -526,7 +527,8 @@ public static Project FromProjectRootElement(ProjectRootElement rootElement, Pro options.LoadSettings, options.EvaluationContext, options.DirectoryCacheFactory, - options.Interactive); + options.Interactive, + options.EvaluationStage); } /// @@ -545,7 +547,8 @@ public static Project FromXmlReader(XmlReader reader, ProjectOptions options) options.LoadSettings, options.EvaluationContext, options.DirectoryCacheFactory, - options.Interactive); + options.Interactive, + options.EvaluationStage); } /// @@ -840,6 +843,16 @@ public bool IsBuildEnabled /// public int LastEvaluationId => implementation.LastEvaluationId; + /// + /// How far evaluation proceeded when this project was last evaluated. + /// When this is not , the project is the result of a + /// partial evaluation (requested via ) and members + /// exposing state from later passes (for example items or targets) throw + /// until the project is re-evaluated via + /// . + /// + public ProjectEvaluationStage EvaluationStage => (implementation as ProjectImpl)?.EvaluationStageInternal ?? ProjectEvaluationStage.Full; + /// /// List of names of the properties that, while global, are still treated as overridable. /// @@ -1873,6 +1886,14 @@ private class ProjectImpl : ProjectLink, IProjectLinkInternal /// private ProjectLoadSettings _loadSettings; + /// + /// The evaluation stage reached by the most recent evaluation. + /// unless the project was created via a requesting a partial evaluation. + /// Retained so that member accessors can fail fast on not-yet-computed state, and so re-evaluation + /// can restore the requested stage. + /// + private ProjectEvaluationStage _evaluationStage = ProjectEvaluationStage.Full; + /// /// The delegate registered with the ProjectRootElement to be called if the file name /// is changed. Retained so that ultimately it can be unregistered. @@ -2226,13 +2247,44 @@ public override IDictionary> ConditionedProperties /// Read-only dictionary of item definitions in this project. /// Keyed by item type. /// - public override IDictionary ItemDefinitions => _data.ItemDefinitions; + public override IDictionary ItemDefinitions + { + get + { + VerifyThrowEvaluationStageReached(ProjectEvaluationStage.ItemDefinitions, nameof(ItemDefinitions)); + return _data.ItemDefinitions; + } + } + + /// + /// The evaluation stage reached by the most recent evaluation. See . + /// + internal ProjectEvaluationStage EvaluationStageInternal => _evaluationStage; + + /// + /// Throws if the most recent evaluation stopped before + /// , meaning the requested member's state was never computed. + /// + private void VerifyThrowEvaluationStageReached(ProjectEvaluationStage requiredStage, string memberName) + { + if (_evaluationStage < requiredStage) + { + ErrorUtilities.ThrowInvalidOperation("OM_PartialEvaluationMemberUnavailable", memberName, _evaluationStage, requiredStage); + } + } /// /// Items in this project, ordered within groups of item types. /// [SuppressMessage("Microsoft.Naming", "CA1721:PropertyNamesShouldNotMatchGetMethods", Justification = "This is a reasonable choice. API review approved")] - public override ICollection Items => new ReadOnlyCollection(_data.Items); + public override ICollection Items + { + get + { + VerifyThrowEvaluationStageReached(ProjectEvaluationStage.Items, nameof(Items)); + return new ReadOnlyCollection(_data.Items); + } + } /// /// Items in this project, ordered within groups of item types, @@ -2247,6 +2299,8 @@ public override ICollection ItemsIgnoringCondition [DebuggerStepThrough] get { + VerifyThrowEvaluationStageReached(ProjectEvaluationStage.Items, nameof(ItemsIgnoringCondition)); + if (!(_data.ShouldEvaluateForDesignTime && _data.CanEvaluateElementsWithFalseConditions)) { ErrorUtilities.ThrowInvalidOperation("OM_NotEvaluatedBecauseShouldEvaluateForDesignTimeIsFalse", nameof(ItemsIgnoringCondition)); @@ -2317,6 +2371,8 @@ public override IDictionary Targets [DebuggerStepThrough] get { + VerifyThrowEvaluationStageReached(ProjectEvaluationStage.Full, nameof(Targets)); + if (_data.Targets == null) { return ReadOnlyEmptyDictionary.Instance; @@ -2383,6 +2439,8 @@ public override ICollection AllEvaluatedItems { get { + VerifyThrowEvaluationStageReached(ProjectEvaluationStage.Items, nameof(AllEvaluatedItems)); + ICollection allEvaluatedItems = _data.AllEvaluatedItems; if (allEvaluatedItems == null) @@ -3125,6 +3183,7 @@ public override IList AddItemFast(string itemType, string unevaluat /// public override ICollection GetItems(string itemType) { + VerifyThrowEvaluationStageReached(ProjectEvaluationStage.Items, nameof(GetItems)); ICollection items = _data.GetItems(itemType); return items; } @@ -3139,6 +3198,7 @@ public override ICollection GetItems(string itemType) /// public override ICollection GetItemsIgnoringCondition(string itemType) { + VerifyThrowEvaluationStageReached(ProjectEvaluationStage.Items, nameof(GetItemsIgnoringCondition)); ICollection items = _data.ItemsIgnoringCondition[itemType]; return items; } @@ -3156,6 +3216,7 @@ public override ICollection GetItemsIgnoringCondition(string itemTy /// public override ICollection GetItemsByEvaluatedInclude(string evaluatedInclude) { + VerifyThrowEvaluationStageReached(ProjectEvaluationStage.Items, nameof(GetItemsByEvaluatedInclude)); ICollection items = _data.GetItemsByEvaluatedInclude(evaluatedInclude); return items; } @@ -3321,6 +3382,15 @@ public override void MarkDirty() /// The to use. See . public override void ReevaluateIfNecessary(EvaluationContext evaluationContext) { + // A public re-evaluation request implies the caller wants a fully-evaluated project. + // A partial evaluation leaves the project non-dirty, so force a re-evaluation to + // compute the remaining passes and restore full behavior. + if (_evaluationStage != ProjectEvaluationStage.Full) + { + _evaluationStage = ProjectEvaluationStage.Full; + _explicitlyMarkedDirty = true; + } + ReevaluateIfNecessary(LoggingService, evaluationContext); } @@ -3723,6 +3793,15 @@ private ProjectInstance CreateProjectInstance( ProjectInstanceSettings settings, EvaluationContext evaluationContext) { + // Materializing a ProjectInstance implies a fully-evaluated project (it may be built, + // and it is labeled Full). If this project was only partially evaluated, upgrade it to + // a full evaluation first so the snapshot is complete rather than silently partial. + if (_evaluationStage != ProjectEvaluationStage.Full) + { + _evaluationStage = ProjectEvaluationStage.Full; + _explicitlyMarkedDirty = true; + } + ReevaluateIfNecessary(loggingServiceForEvaluation, evaluationContext); return new ProjectInstance(_data, DirectoryPath, FullPath, ProjectCollection.HostServices, ProjectCollection.EnvironmentProperties, settings); @@ -3752,7 +3831,8 @@ private void Reevaluate( evaluationContext.SdkResolverService, BuildEventContext.InvalidSubmissionId, evaluationContext, - _interactive); + _interactive, + _evaluationStage); Assumed.NotEqual(LastEvaluationId, BuildEventContext.InvalidEvaluationId, "Evaluation should produce an evaluation ID"); @@ -3785,7 +3865,7 @@ private void Reevaluate( /// Global properties may be null. /// Tools version may be null. /// - internal void Initialize(IDictionary globalProperties, string toolsVersion, string subToolsetVersion, ProjectLoadSettings loadSettings, EvaluationContext evaluationContext, bool interactive) + internal void Initialize(IDictionary globalProperties, string toolsVersion, string subToolsetVersion, ProjectLoadSettings loadSettings, EvaluationContext evaluationContext, bool interactive, ProjectEvaluationStage evaluationStage = ProjectEvaluationStage.Full) { Xml.MarkAsExplicitlyLoaded(); @@ -3821,10 +3901,14 @@ internal void Initialize(IDictionary globalProperties, string to _loadSettings = loadSettings; _interactive = interactive; + _evaluationStage = evaluationStage; Assumed.Equal(LastEvaluationId, BuildEventContext.InvalidEvaluationId, "This is the first evaluation therefore the last evaluation id is invalid"); - ReevaluateIfNecessary(evaluationContext); + // Call the private overload directly so an initial partial evaluation is honored. The + // public ReevaluateIfNecessary(EvaluationContext) override intentionally upgrades a + // partial project to a full evaluation, which must not happen during initial construction. + ReevaluateIfNecessary(LoggingService, evaluationContext); Assumed.NotEqual(LastEvaluationId, BuildEventContext.InvalidEvaluationId, "Last evaluation ID must be valid after the first evaluation"); diff --git a/src/Build/Definition/ProjectCollection.cs b/src/Build/Definition/ProjectCollection.cs index 4668591ab06..326db40e416 100644 --- a/src/Build/Definition/ProjectCollection.cs +++ b/src/Build/Definition/ProjectCollection.cs @@ -1323,6 +1323,16 @@ public Project LoadProject(string fileName, IDictionary globalPr string effectiveToolsVersion = Utilities.GenerateToolsVersionToUse(toolsVersion, toolsVersionFromProject, GetToolset, DefaultToolsVersion, out _); Project project = _loadedProjects.GetMatchingProjectIfAny(fileName, globalProperties, effectiveToolsVersion); + // A cached project only satisfies a (full) LoadProject if it was fully evaluated. If a matching + // project exists but was only partially evaluated, upgrade it in place (re-evaluate) so the same + // cache slot is reused rather than returning stale partial state or creating a duplicate entry. + // This mutation runs under the per-path load lock taken above, so concurrent loads of the same + // path cannot upgrade (and thus re-evaluate) the same project instance simultaneously. + if (project is not null && project.EvaluationStage < ProjectEvaluationStage.Full) + { + project.ReevaluateIfNecessary(); + } + // The Project constructor adds itself to our collection, it is not done by us project ??= new Project(fileName, globalProperties, effectiveToolsVersion, this); @@ -2042,9 +2052,9 @@ internal Project GetMatchingProjectIfAny(string fullPath, IDictionary diff --git a/src/Build/Definition/ProjectEvaluationStage.cs b/src/Build/Definition/ProjectEvaluationStage.cs new file mode 100644 index 00000000000..3e3269cd25b --- /dev/null +++ b/src/Build/Definition/ProjectEvaluationStage.cs @@ -0,0 +1,58 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.Build.Evaluation +{ + /// + /// Specifies how far project evaluation should proceed. + /// + /// + /// + /// MSBuild evaluation runs a fixed sequence of passes. Callers that only need data produced by + /// an early pass (for example, a property value) can request a partial evaluation that stops + /// after that pass, avoiding the cost of the later passes (item globbing, using-tasks, and + /// target registration). + /// + /// + /// The values are ordered by how much of evaluation is performed. A larger value performs strictly + /// more work than a smaller one. The default is , which preserves the historical + /// behavior of running every pass. + /// + /// + /// Reading state that a partial evaluation did not produce (for example, reading items after + /// stopping at ) throws . + /// + /// + public enum ProjectEvaluationStage + { + /// + /// Evaluate initial properties, properties, and imports (passes 0 and 1), then stop. + /// Property values are final and equivalent to a full evaluation. Items, item definitions, + /// using-tasks, and targets are not available. + /// + Properties = 1, + + /// + /// Evaluate through item definitions (pass 2), then stop. Includes everything from + /// . Items, using-tasks, and targets are not available. + /// + ItemDefinitions = 2, + + /// + /// Evaluate through items (passes 3 and 3.1), then stop. Includes everything from + /// . Using-tasks and targets are not available. + /// + Items = 3, + + /// + /// Evaluate through using-tasks (pass 4), then stop. Includes everything from + /// . Targets are not available. + /// + UsingTasks = 4, + + /// + /// Perform a complete evaluation, including target registration (pass 5). This is the default. + /// + Full = int.MaxValue, + } +} diff --git a/src/Build/Definition/ProjectOptions.cs b/src/Build/Definition/ProjectOptions.cs index c03507b3d3c..8193bb56321 100644 --- a/src/Build/Definition/ProjectOptions.cs +++ b/src/Build/Definition/ProjectOptions.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System; using System.Collections.Generic; using Microsoft.Build.Evaluation; using Microsoft.Build.Evaluation.Context; @@ -54,5 +55,28 @@ public class ProjectOptions /// Gets or sets a value indicating if loading the project is allowed to interact with the user. /// public bool Interactive { get; set; } + + /// + /// The controlling how far evaluation should proceed. + /// Defaults to (a complete evaluation). + /// + public ProjectEvaluationStage EvaluationStage + { + get => _evaluationStage; + set + { + // The enum intentionally leaves a large numeric gap between UsingTasks and Full. Reject any + // undefined value so a stray stage cannot slip through and cause the evaluator to run every + // pass while the object-model guards still (incorrectly) report the state as unavailable. + if (!Enum.IsDefined(typeof(ProjectEvaluationStage), value)) + { + throw new ArgumentOutOfRangeException(nameof(value), value, null); + } + + _evaluationStage = value; + } + } + + private ProjectEvaluationStage _evaluationStage = ProjectEvaluationStage.Full; } } diff --git a/src/Build/Evaluation/Evaluator.cs b/src/Build/Evaluation/Evaluator.cs index 99c874ac504..a17ca8e2e7c 100644 --- a/src/Build/Evaluation/Evaluator.cs +++ b/src/Build/Evaluation/Evaluator.cs @@ -139,6 +139,11 @@ internal class Evaluator /// private readonly ProjectLoadSettings _loadSettings; + /// + /// How far evaluation should proceed. runs every pass. + /// + private readonly ProjectEvaluationStage _evaluationStage; + /// /// The maximum number of nodes to report for evaluation. /// @@ -223,7 +228,8 @@ private Evaluator( bool profileEvaluation, bool interactive, ILoggingService loggingService, - BuildEventContext buildEventContext) + BuildEventContext buildEventContext, + ProjectEvaluationStage evaluationStage) { Assumed.NotNull(data); Assumed.NotNull(projectRootElementCache); @@ -263,6 +269,7 @@ private Evaluator( _projectSupportsReturnsAttribute = new Dictionary(); _projectRootElement = projectRootElement; _loadSettings = loadSettings; + _evaluationStage = evaluationStage; _maxNodeCount = maxNodeCount; _environmentProperties = environmentProperties; _propertiesFromCommandLine = propertiesFromCommandLine ?? []; @@ -324,7 +331,8 @@ internal static void Evaluate( ISdkResolverService sdkResolverService, int submissionId, EvaluationContext evaluationContext, - bool interactive = false) + bool interactive = false, + ProjectEvaluationStage evaluationStage = ProjectEvaluationStage.Full) { MSBuildEventSource.Log.EvaluateStart(root.ProjectFileLocation.File); var profileEvaluation = (loadSettings & ProjectLoadSettings.ProfileEvaluation) != 0 || loggingService.IncludeEvaluationProfile; @@ -346,7 +354,8 @@ internal static void Evaluate( profileEvaluation, interactive, loggingService, - buildEventContext); + buildEventContext, + evaluationStage); try { @@ -685,6 +694,13 @@ private void Evaluate() _data.InitialTargets = initialTargets; MSBuildEventSource.Log.EvaluatePass1Stop(projectFile); + + if (_evaluationStage <= ProjectEvaluationStage.Properties) + { + _data.FinishEvaluation(); + return; + } + // Pass2: evaluate item definitions // Don't box via IEnumerator and foreach; cache count so not to evaluate via interface each iteration MSBuildEventSource.Log.EvaluatePass2Start(projectFile); @@ -699,6 +715,13 @@ private void Evaluate() } } MSBuildEventSource.Log.EvaluatePass2Stop(projectFile); + + if (_evaluationStage <= ProjectEvaluationStage.ItemDefinitions) + { + _data.FinishEvaluation(); + return; + } + LazyItemEvaluator lazyEvaluator = null; using (_evaluationProfiler.TrackPass(EvaluationPass.Items)) { @@ -746,6 +769,12 @@ private void Evaluate() MSBuildEventSource.Log.EvaluatePass3Stop(projectFile); + if (_evaluationStage <= ProjectEvaluationStage.Items) + { + _data.FinishEvaluation(); + return; + } + // Pass4: evaluate using-tasks MSBuildEventSource.Log.EvaluatePass4Start(projectFile); using (_evaluationProfiler.TrackPass(EvaluationPass.UsingTasks)) @@ -760,6 +789,14 @@ private void Evaluate() _evaluationContext.FileSystem); } + MSBuildEventSource.Log.EvaluatePass4Stop(projectFile); + + if (_evaluationStage <= ProjectEvaluationStage.UsingTasks) + { + _data.FinishEvaluation(); + return; + } + // If there was no DefaultTargets attribute found in the depth first pass, // use the name of the first target. If there isn't any target, don't error until build time. @@ -778,7 +815,6 @@ private void Evaluate() Dictionary> targetsWhichRunAfterByTarget = new Dictionary>(StringComparer.OrdinalIgnoreCase); LinkedList activeTargetsByEvaluationOrder = new LinkedList(); Dictionary> activeTargets = new Dictionary>(StringComparer.OrdinalIgnoreCase); - MSBuildEventSource.Log.EvaluatePass4Stop(projectFile); using (_evaluationProfiler.TrackPass(EvaluationPass.Targets)) { diff --git a/src/Build/Instance/ProjectInstance.cs b/src/Build/Instance/ProjectInstance.cs index 23385240c11..cf480f22cd6 100644 --- a/src/Build/Instance/ProjectInstance.cs +++ b/src/Build/Instance/ProjectInstance.cs @@ -194,6 +194,13 @@ public class ProjectInstance : IPropertyProvider, IItem private bool _translateEntireState; private int _evaluationId = BuildEventContext.InvalidEvaluationId; + /// + /// How far evaluation proceeded when this instance was produced. Defaults to + /// . A partial value means later-pass state + /// (items, targets, and so on) was not produced and accessing it will throw. + /// + private ProjectEvaluationStage _evaluationStage = ProjectEvaluationStage.Full; + /// /// The property and item filter used when creating this instance, or null if this is not a filtered copy /// of another ProjectInstance. @@ -297,9 +304,11 @@ internal ProjectInstance(string projectFile, IDictionary globalP /// The context to use for evaluation. /// The directory cache factory to use for file I/O. /// Indicates if loading the project is allowed to interact with the user. + /// The stage after which to stop evaluation. /// A new project instance private ProjectInstance(string projectFile, IDictionary globalProperties, string toolsVersion, string subToolsetVersion, ProjectCollection projectCollection, - ProjectLoadSettings? projectLoadSettings, EvaluationContext evaluationContext, IDirectoryCacheFactory directoryCacheFactory, bool interactive) + ProjectLoadSettings? projectLoadSettings, EvaluationContext evaluationContext, IDirectoryCacheFactory directoryCacheFactory, bool interactive, + ProjectEvaluationStage evaluationStage = ProjectEvaluationStage.Full) { ArgumentException.ThrowIfNullOrEmpty(projectFile); ErrorUtilities.VerifyThrowArgumentLengthIfNotNull(toolsVersion, nameof(toolsVersion)); @@ -317,7 +326,7 @@ private ProjectInstance(string projectFile, IDictionary globalPr ProjectRootElement xml = ProjectRootElement.OpenProjectOrSolution(projectFile, globalProperties, toolsVersion, buildParameters.ProjectRootElementCache, true /*Explicitly Loaded*/); Initialize(xml, globalProperties, toolsVersion, subToolsetVersion, 0 /* no solution version provided */, buildParameters, projectCollection.LoggingService, buildEventContext, - projectLoadSettings: projectLoadSettings, evaluationContext: evaluationContext, directoryCacheFactory: directoryCacheFactory); + projectLoadSettings: projectLoadSettings, evaluationContext: evaluationContext, directoryCacheFactory: directoryCacheFactory, evaluationStage: evaluationStage); } /// @@ -539,9 +548,11 @@ static List GetImportFullPathsIncludingDuplicates(ObjectModelRemoting.Pr /// The context to use for evaluation. /// The directory cache factory to use for file I/O. /// Indicates if loading the project is allowed to interact with the user. + /// The stage after which to stop evaluation. /// A new project instance private ProjectInstance(ProjectRootElement xml, IDictionary globalProperties, string toolsVersion, string subToolsetVersion, ProjectCollection projectCollection, - ProjectLoadSettings? projectLoadSettings, EvaluationContext evaluationContext, IDirectoryCacheFactory directoryCacheFactory, bool interactive) + ProjectLoadSettings? projectLoadSettings, EvaluationContext evaluationContext, IDirectoryCacheFactory directoryCacheFactory, bool interactive, + ProjectEvaluationStage evaluationStage = ProjectEvaluationStage.Full) { BuildEventContext buildEventContext = new BuildEventContext(0, BuildEventContext.InvalidTargetId, BuildEventContext.InvalidProjectContextId, BuildEventContext.InvalidTaskId); @@ -551,7 +562,7 @@ private ProjectInstance(ProjectRootElement xml, IDictionary glob }; Initialize(xml, globalProperties, toolsVersion, subToolsetVersion, 0 /* no solution version specified */, buildParameters, projectCollection.LoggingService, buildEventContext, - projectLoadSettings: projectLoadSettings, evaluationContext: evaluationContext, directoryCacheFactory: directoryCacheFactory); + projectLoadSettings: projectLoadSettings, evaluationContext: evaluationContext, directoryCacheFactory: directoryCacheFactory, evaluationStage: evaluationStage); } /// @@ -926,7 +937,8 @@ public static ProjectInstance FromFile(string file, ProjectOptions options) options.LoadSettings, options.EvaluationContext, options.DirectoryCacheFactory, - options.Interactive); + options.Interactive, + options.EvaluationStage); } /// @@ -945,7 +957,8 @@ public static ProjectInstance FromProjectRootElement(ProjectRootElement rootElem options.LoadSettings, options.EvaluationContext, options.DirectoryCacheFactory, - options.Interactive); + options.Interactive, + options.EvaluationStage); } /// @@ -1174,6 +1187,7 @@ public ICollection Items [DebuggerStepThrough] get { + VerifyThrowEvaluationStageReached(ProjectEvaluationStage.Items, nameof(Items)); return (_items == null) ? (ICollection)ReadOnlyEmptyCollection.Instance : new ReadOnlyCollection(_items); @@ -1213,6 +1227,30 @@ public int EvaluationId set { _evaluationId = value; } } + /// + /// How far evaluation proceeded when this instance was produced. + /// When this is not , the instance is the result of a + /// partial evaluation and members exposing state from later passes (for example items or targets) + /// throw . + /// + public ProjectEvaluationStage EvaluationStage + { + get { return _evaluationStage; } + } + + /// + /// Throws if this instance was produced by a partial + /// evaluation that stopped before , meaning the requested + /// member's state was never computed. + /// + private void VerifyThrowEvaluationStageReached(ProjectEvaluationStage requiredStage, string memberName) + { + if (_evaluationStage < requiredStage) + { + ErrorUtilities.ThrowInvalidOperation("OM_PartialEvaluationMemberUnavailable", memberName, _evaluationStage, requiredStage); + } + } + /// /// The project's root directory, for evaluation of relative paths and /// setting the current directory during build. @@ -1242,9 +1280,11 @@ public string FullPath /// public IDictionary ItemDefinitions { - [DebuggerStepThrough] get - { return _itemDefinitions; } + { + VerifyThrowEvaluationStageReached(ProjectEvaluationStage.ItemDefinitions, nameof(ItemDefinitions)); + return _itemDefinitions; + } } /// @@ -1268,8 +1308,16 @@ public IDictionary ItemDefinitions /// public List DefaultTargets { - get { return _defaultTargets; } - private set { _defaultTargets = value; } + get + { + VerifyThrowEvaluationStageReached(ProjectEvaluationStage.Full, nameof(DefaultTargets)); + return _defaultTargets; + } + + private set + { + _defaultTargets = value; + } } /// @@ -1292,7 +1340,10 @@ public IDictionary Targets { [DebuggerStepThrough] get - { return _targets; } + { + VerifyThrowEvaluationStageReached(ProjectEvaluationStage.Full, nameof(Targets)); + return _targets; + } } /// @@ -2079,6 +2130,8 @@ public ProjectItemInstance AddItem(string itemType, string evaluatedInclude, IEn /// public ICollection GetItems(string itemType) { + VerifyThrowEvaluationStageReached(ProjectEvaluationStage.Items, nameof(GetItems)); + // GetItems already returns a readonly collection return ((IItemProvider)this).GetItems(itemType); } @@ -3199,7 +3252,8 @@ private void Initialize( int submissionId = BuildEventContext.InvalidSubmissionId, ProjectLoadSettings? projectLoadSettings = null, EvaluationContext evaluationContext = null, - IDirectoryCacheFactory directoryCacheFactory = null) + IDirectoryCacheFactory directoryCacheFactory = null, + ProjectEvaluationStage evaluationStage = ProjectEvaluationStage.Full) { ArgumentNullException.ThrowIfNull(xml); ErrorUtilities.VerifyThrowArgumentLengthIfNotNull(explicitToolsVersion, "toolsVersion"); @@ -3305,7 +3359,10 @@ private void Initialize( sdkResolverService ?? evaluationContext.SdkResolverService, /* Use override ISdkResolverService if specified */ submissionId, evaluationContext, - interactive: buildParameters.Interactive); + interactive: buildParameters.Interactive, + evaluationStage: evaluationStage); + + _evaluationStage = evaluationStage; Assumed.NotEqual(EvaluationId, BuildEventContext.InvalidEvaluationId, "Evaluation should produce an evaluation ID"); } diff --git a/src/Build/Microsoft.Build.csproj b/src/Build/Microsoft.Build.csproj index c4dfdadab1c..7ee6586608e 100644 --- a/src/Build/Microsoft.Build.csproj +++ b/src/Build/Microsoft.Build.csproj @@ -313,6 +313,7 @@ + diff --git a/src/Build/Resources/Strings.resx b/src/Build/Resources/Strings.resx index 2a542f59adc..4b44ec202d2 100644 --- a/src/Build/Resources/Strings.resx +++ b/src/Build/Resources/Strings.resx @@ -1587,6 +1587,14 @@ Utilization: {0} Average Utilization: {1:###.0} This collection cannot convert from the specified value to the backing value. + + The "{0}" member is not available because the project was only partially evaluated (evaluation stopped after the "{1}" stage). Re-evaluate the project with at least the "{2}" evaluation stage to access this member. + UE: This message is shown when the user reads a member (for example items or targets) that a partial evaluation did not compute. {0} is the member name, {1} is the ProjectEvaluationStage the project was evaluated to, {2} is the minimum ProjectEvaluationStage required to access the member. + + + The project instance cannot be built because it was only partially evaluated (evaluation stopped after the "{0}" stage). A full evaluation is required to build a project. + UE: This message is shown when the user tries to build a ProjectInstance produced by a partial evaluation. {0} is the ProjectEvaluationStage the instance was evaluated to. + The "{0}" property name is reserved. UE: This message is shown when the user tries to redefine one of the reserved MSBuild properties e.g. $(MSBuildProjectFile) through the object model diff --git a/src/Build/Resources/xlf/Strings.cs.xlf b/src/Build/Resources/xlf/Strings.cs.xlf index 729a2cbe004..bf4676b1094 100644 --- a/src/Build/Resources/xlf/Strings.cs.xlf +++ b/src/Build/Resources/xlf/Strings.cs.xlf @@ -719,6 +719,16 @@ Klíčové slovo MyClass nejde použít vně položky <Target>. + + The project instance cannot be built because it was only partially evaluated (evaluation stopped after the "{0}" stage). A full evaluation is required to build a project. + The project instance cannot be built because it was only partially evaluated (evaluation stopped after the "{0}" stage). A full evaluation is required to build a project. + UE: This message is shown when the user tries to build a ProjectInstance produced by a partial evaluation. {0} is the ProjectEvaluationStage the instance was evaluated to. + + + The "{0}" member is not available because the project was only partially evaluated (evaluation stopped after the "{1}" stage). Re-evaluate the project with at least the "{2}" evaluation stage to access this member. + The "{0}" member is not available because the project was only partially evaluated (evaluation stopped after the "{1}" stage). Re-evaluate the project with at least the "{2}" evaluation stage to access this member. + UE: This message is shown when the user reads a member (for example items or targets) that a partial evaluation did not compute. {0} is the member name, {1} is the ProjectEvaluationStage the project was evaluated to, {2} is the minimum ProjectEvaluationStage required to access the member. + Method {0} cannot be called with a collection containing null or empty target names. Metoda {0} se nedá zavolat s kolekcí, která obsahuje prázdné cílové názvy nebo názvy null. diff --git a/src/Build/Resources/xlf/Strings.de.xlf b/src/Build/Resources/xlf/Strings.de.xlf index 434f9c4bd37..a9f05e28f94 100644 --- a/src/Build/Resources/xlf/Strings.de.xlf +++ b/src/Build/Resources/xlf/Strings.de.xlf @@ -719,6 +719,16 @@ MatchOnMetadata kann nicht außerhalb eines <Ziels> verwendet werden. + + The project instance cannot be built because it was only partially evaluated (evaluation stopped after the "{0}" stage). A full evaluation is required to build a project. + The project instance cannot be built because it was only partially evaluated (evaluation stopped after the "{0}" stage). A full evaluation is required to build a project. + UE: This message is shown when the user tries to build a ProjectInstance produced by a partial evaluation. {0} is the ProjectEvaluationStage the instance was evaluated to. + + + The "{0}" member is not available because the project was only partially evaluated (evaluation stopped after the "{1}" stage). Re-evaluate the project with at least the "{2}" evaluation stage to access this member. + The "{0}" member is not available because the project was only partially evaluated (evaluation stopped after the "{1}" stage). Re-evaluate the project with at least the "{2}" evaluation stage to access this member. + UE: This message is shown when the user reads a member (for example items or targets) that a partial evaluation did not compute. {0} is the member name, {1} is the ProjectEvaluationStage the project was evaluated to, {2} is the minimum ProjectEvaluationStage required to access the member. + Method {0} cannot be called with a collection containing null or empty target names. Die Methode "{0}" kann nicht mit einer Sammlung aufgerufen werden, die NULL oder leere Zielnamen enthält. diff --git a/src/Build/Resources/xlf/Strings.es.xlf b/src/Build/Resources/xlf/Strings.es.xlf index 795e7844be0..f4fdafdfbbf 100644 --- a/src/Build/Resources/xlf/Strings.es.xlf +++ b/src/Build/Resources/xlf/Strings.es.xlf @@ -719,6 +719,16 @@ MatchOnMetadata no se puede usar fuera de un elemento <Target>. + + The project instance cannot be built because it was only partially evaluated (evaluation stopped after the "{0}" stage). A full evaluation is required to build a project. + The project instance cannot be built because it was only partially evaluated (evaluation stopped after the "{0}" stage). A full evaluation is required to build a project. + UE: This message is shown when the user tries to build a ProjectInstance produced by a partial evaluation. {0} is the ProjectEvaluationStage the instance was evaluated to. + + + The "{0}" member is not available because the project was only partially evaluated (evaluation stopped after the "{1}" stage). Re-evaluate the project with at least the "{2}" evaluation stage to access this member. + The "{0}" member is not available because the project was only partially evaluated (evaluation stopped after the "{1}" stage). Re-evaluate the project with at least the "{2}" evaluation stage to access this member. + UE: This message is shown when the user reads a member (for example items or targets) that a partial evaluation did not compute. {0} is the member name, {1} is the ProjectEvaluationStage the project was evaluated to, {2} is the minimum ProjectEvaluationStage required to access the member. + Method {0} cannot be called with a collection containing null or empty target names. No se puede llamar al método {0} con una colección que contiene nombres de destino nulos o vacíos. diff --git a/src/Build/Resources/xlf/Strings.fr.xlf b/src/Build/Resources/xlf/Strings.fr.xlf index 7da50ed1690..5c309a7d7f1 100644 --- a/src/Build/Resources/xlf/Strings.fr.xlf +++ b/src/Build/Resources/xlf/Strings.fr.xlf @@ -719,6 +719,16 @@ Impossible d'utiliser MatchOnMetadata en dehors de <Target>. + + The project instance cannot be built because it was only partially evaluated (evaluation stopped after the "{0}" stage). A full evaluation is required to build a project. + The project instance cannot be built because it was only partially evaluated (evaluation stopped after the "{0}" stage). A full evaluation is required to build a project. + UE: This message is shown when the user tries to build a ProjectInstance produced by a partial evaluation. {0} is the ProjectEvaluationStage the instance was evaluated to. + + + The "{0}" member is not available because the project was only partially evaluated (evaluation stopped after the "{1}" stage). Re-evaluate the project with at least the "{2}" evaluation stage to access this member. + The "{0}" member is not available because the project was only partially evaluated (evaluation stopped after the "{1}" stage). Re-evaluate the project with at least the "{2}" evaluation stage to access this member. + UE: This message is shown when the user reads a member (for example items or targets) that a partial evaluation did not compute. {0} is the member name, {1} is the ProjectEvaluationStage the project was evaluated to, {2} is the minimum ProjectEvaluationStage required to access the member. + Method {0} cannot be called with a collection containing null or empty target names. Impossible d'appeler la méthode {0} avec une collection contenant des noms de cibles qui ont une valeur null ou qui sont vides. diff --git a/src/Build/Resources/xlf/Strings.it.xlf b/src/Build/Resources/xlf/Strings.it.xlf index ee9b16b8cf6..12a99a9ae11 100644 --- a/src/Build/Resources/xlf/Strings.it.xlf +++ b/src/Build/Resources/xlf/Strings.it.xlf @@ -719,6 +719,16 @@ MatchOnMetadata non può essere usato all'esterno di un elemento <Target>. + + The project instance cannot be built because it was only partially evaluated (evaluation stopped after the "{0}" stage). A full evaluation is required to build a project. + The project instance cannot be built because it was only partially evaluated (evaluation stopped after the "{0}" stage). A full evaluation is required to build a project. + UE: This message is shown when the user tries to build a ProjectInstance produced by a partial evaluation. {0} is the ProjectEvaluationStage the instance was evaluated to. + + + The "{0}" member is not available because the project was only partially evaluated (evaluation stopped after the "{1}" stage). Re-evaluate the project with at least the "{2}" evaluation stage to access this member. + The "{0}" member is not available because the project was only partially evaluated (evaluation stopped after the "{1}" stage). Re-evaluate the project with at least the "{2}" evaluation stage to access this member. + UE: This message is shown when the user reads a member (for example items or targets) that a partial evaluation did not compute. {0} is the member name, {1} is the ProjectEvaluationStage the project was evaluated to, {2} is the minimum ProjectEvaluationStage required to access the member. + Method {0} cannot be called with a collection containing null or empty target names. Non è possibile chiamare il metodo {0} con una raccolta contenente nomi di destinazione Null o vuoti. diff --git a/src/Build/Resources/xlf/Strings.ja.xlf b/src/Build/Resources/xlf/Strings.ja.xlf index 8dfb4ea14e2..d86d232361a 100644 --- a/src/Build/Resources/xlf/Strings.ja.xlf +++ b/src/Build/Resources/xlf/Strings.ja.xlf @@ -719,6 +719,16 @@ MatchOnMetadata を <Target> の外で使用することはできません。 + + The project instance cannot be built because it was only partially evaluated (evaluation stopped after the "{0}" stage). A full evaluation is required to build a project. + The project instance cannot be built because it was only partially evaluated (evaluation stopped after the "{0}" stage). A full evaluation is required to build a project. + UE: This message is shown when the user tries to build a ProjectInstance produced by a partial evaluation. {0} is the ProjectEvaluationStage the instance was evaluated to. + + + The "{0}" member is not available because the project was only partially evaluated (evaluation stopped after the "{1}" stage). Re-evaluate the project with at least the "{2}" evaluation stage to access this member. + The "{0}" member is not available because the project was only partially evaluated (evaluation stopped after the "{1}" stage). Re-evaluate the project with at least the "{2}" evaluation stage to access this member. + UE: This message is shown when the user reads a member (for example items or targets) that a partial evaluation did not compute. {0} is the member name, {1} is the ProjectEvaluationStage the project was evaluated to, {2} is the minimum ProjectEvaluationStage required to access the member. + Method {0} cannot be called with a collection containing null or empty target names. Null または空のターゲット名を含むコレクションを指定してメソッド {0} を呼び出すことはできません。 diff --git a/src/Build/Resources/xlf/Strings.ko.xlf b/src/Build/Resources/xlf/Strings.ko.xlf index ed4c528d256..d63a3e0321a 100644 --- a/src/Build/Resources/xlf/Strings.ko.xlf +++ b/src/Build/Resources/xlf/Strings.ko.xlf @@ -719,6 +719,16 @@ MatchOnMetadata는 <Target> 외부에 사용할 수 없습니다. + + The project instance cannot be built because it was only partially evaluated (evaluation stopped after the "{0}" stage). A full evaluation is required to build a project. + The project instance cannot be built because it was only partially evaluated (evaluation stopped after the "{0}" stage). A full evaluation is required to build a project. + UE: This message is shown when the user tries to build a ProjectInstance produced by a partial evaluation. {0} is the ProjectEvaluationStage the instance was evaluated to. + + + The "{0}" member is not available because the project was only partially evaluated (evaluation stopped after the "{1}" stage). Re-evaluate the project with at least the "{2}" evaluation stage to access this member. + The "{0}" member is not available because the project was only partially evaluated (evaluation stopped after the "{1}" stage). Re-evaluate the project with at least the "{2}" evaluation stage to access this member. + UE: This message is shown when the user reads a member (for example items or targets) that a partial evaluation did not compute. {0} is the member name, {1} is the ProjectEvaluationStage the project was evaluated to, {2} is the minimum ProjectEvaluationStage required to access the member. + Method {0} cannot be called with a collection containing null or empty target names. null 또는 빈 대상 이름을 포함하는 컬렉션을 사용하여 {0} 메서드를 호출할 수 없습니다. diff --git a/src/Build/Resources/xlf/Strings.pl.xlf b/src/Build/Resources/xlf/Strings.pl.xlf index f06a86308dd..cab60385d24 100644 --- a/src/Build/Resources/xlf/Strings.pl.xlf +++ b/src/Build/Resources/xlf/Strings.pl.xlf @@ -719,6 +719,16 @@ Nie można użyć elementu MatchOnMetadata poza elementem <Target>. + + The project instance cannot be built because it was only partially evaluated (evaluation stopped after the "{0}" stage). A full evaluation is required to build a project. + The project instance cannot be built because it was only partially evaluated (evaluation stopped after the "{0}" stage). A full evaluation is required to build a project. + UE: This message is shown when the user tries to build a ProjectInstance produced by a partial evaluation. {0} is the ProjectEvaluationStage the instance was evaluated to. + + + The "{0}" member is not available because the project was only partially evaluated (evaluation stopped after the "{1}" stage). Re-evaluate the project with at least the "{2}" evaluation stage to access this member. + The "{0}" member is not available because the project was only partially evaluated (evaluation stopped after the "{1}" stage). Re-evaluate the project with at least the "{2}" evaluation stage to access this member. + UE: This message is shown when the user reads a member (for example items or targets) that a partial evaluation did not compute. {0} is the member name, {1} is the ProjectEvaluationStage the project was evaluated to, {2} is the minimum ProjectEvaluationStage required to access the member. + Method {0} cannot be called with a collection containing null or empty target names. Metody {0} nie można wywołać przy użyciu kolekcji zawierającej nazwy docelowe o wartości null lub puste. diff --git a/src/Build/Resources/xlf/Strings.pt-BR.xlf b/src/Build/Resources/xlf/Strings.pt-BR.xlf index e7dd76b89a9..7f0ff8610fd 100644 --- a/src/Build/Resources/xlf/Strings.pt-BR.xlf +++ b/src/Build/Resources/xlf/Strings.pt-BR.xlf @@ -719,6 +719,16 @@ MatchOnMetadata não pode ser usado fora de um <Target>. + + The project instance cannot be built because it was only partially evaluated (evaluation stopped after the "{0}" stage). A full evaluation is required to build a project. + The project instance cannot be built because it was only partially evaluated (evaluation stopped after the "{0}" stage). A full evaluation is required to build a project. + UE: This message is shown when the user tries to build a ProjectInstance produced by a partial evaluation. {0} is the ProjectEvaluationStage the instance was evaluated to. + + + The "{0}" member is not available because the project was only partially evaluated (evaluation stopped after the "{1}" stage). Re-evaluate the project with at least the "{2}" evaluation stage to access this member. + The "{0}" member is not available because the project was only partially evaluated (evaluation stopped after the "{1}" stage). Re-evaluate the project with at least the "{2}" evaluation stage to access this member. + UE: This message is shown when the user reads a member (for example items or targets) that a partial evaluation did not compute. {0} is the member name, {1} is the ProjectEvaluationStage the project was evaluated to, {2} is the minimum ProjectEvaluationStage required to access the member. + Method {0} cannot be called with a collection containing null or empty target names. O método {0} não pode ser chamado com uma coleção que contém nomes de destino nulos ou vazios. diff --git a/src/Build/Resources/xlf/Strings.ru.xlf b/src/Build/Resources/xlf/Strings.ru.xlf index ec0af4b82f8..7e058d25d74 100644 --- a/src/Build/Resources/xlf/Strings.ru.xlf +++ b/src/Build/Resources/xlf/Strings.ru.xlf @@ -719,6 +719,16 @@ MatchOnMetadata не может использоваться вне <Target>. + + The project instance cannot be built because it was only partially evaluated (evaluation stopped after the "{0}" stage). A full evaluation is required to build a project. + The project instance cannot be built because it was only partially evaluated (evaluation stopped after the "{0}" stage). A full evaluation is required to build a project. + UE: This message is shown when the user tries to build a ProjectInstance produced by a partial evaluation. {0} is the ProjectEvaluationStage the instance was evaluated to. + + + The "{0}" member is not available because the project was only partially evaluated (evaluation stopped after the "{1}" stage). Re-evaluate the project with at least the "{2}" evaluation stage to access this member. + The "{0}" member is not available because the project was only partially evaluated (evaluation stopped after the "{1}" stage). Re-evaluate the project with at least the "{2}" evaluation stage to access this member. + UE: This message is shown when the user reads a member (for example items or targets) that a partial evaluation did not compute. {0} is the member name, {1} is the ProjectEvaluationStage the project was evaluated to, {2} is the minimum ProjectEvaluationStage required to access the member. + Method {0} cannot be called with a collection containing null or empty target names. Метод {0} не может быть вызван с коллекцией, содержащей целевые имена, которые пусты или равны NULL. diff --git a/src/Build/Resources/xlf/Strings.tr.xlf b/src/Build/Resources/xlf/Strings.tr.xlf index eda7c668120..95d803a02ed 100644 --- a/src/Build/Resources/xlf/Strings.tr.xlf +++ b/src/Build/Resources/xlf/Strings.tr.xlf @@ -719,6 +719,16 @@ MatchOnMetadata bir <Target> dışında kullanılamaz. + + The project instance cannot be built because it was only partially evaluated (evaluation stopped after the "{0}" stage). A full evaluation is required to build a project. + The project instance cannot be built because it was only partially evaluated (evaluation stopped after the "{0}" stage). A full evaluation is required to build a project. + UE: This message is shown when the user tries to build a ProjectInstance produced by a partial evaluation. {0} is the ProjectEvaluationStage the instance was evaluated to. + + + The "{0}" member is not available because the project was only partially evaluated (evaluation stopped after the "{1}" stage). Re-evaluate the project with at least the "{2}" evaluation stage to access this member. + The "{0}" member is not available because the project was only partially evaluated (evaluation stopped after the "{1}" stage). Re-evaluate the project with at least the "{2}" evaluation stage to access this member. + UE: This message is shown when the user reads a member (for example items or targets) that a partial evaluation did not compute. {0} is the member name, {1} is the ProjectEvaluationStage the project was evaluated to, {2} is the minimum ProjectEvaluationStage required to access the member. + Method {0} cannot be called with a collection containing null or empty target names. {0} metosu null veya boş hedef adları içeren bir koleksiyonla çağrılamaz. diff --git a/src/Build/Resources/xlf/Strings.zh-Hans.xlf b/src/Build/Resources/xlf/Strings.zh-Hans.xlf index 9ff8a7e8f54..364348bf074 100644 --- a/src/Build/Resources/xlf/Strings.zh-Hans.xlf +++ b/src/Build/Resources/xlf/Strings.zh-Hans.xlf @@ -719,6 +719,16 @@ MatchOnMetadata 不能在 <Target> 外部使用。 + + The project instance cannot be built because it was only partially evaluated (evaluation stopped after the "{0}" stage). A full evaluation is required to build a project. + The project instance cannot be built because it was only partially evaluated (evaluation stopped after the "{0}" stage). A full evaluation is required to build a project. + UE: This message is shown when the user tries to build a ProjectInstance produced by a partial evaluation. {0} is the ProjectEvaluationStage the instance was evaluated to. + + + The "{0}" member is not available because the project was only partially evaluated (evaluation stopped after the "{1}" stage). Re-evaluate the project with at least the "{2}" evaluation stage to access this member. + The "{0}" member is not available because the project was only partially evaluated (evaluation stopped after the "{1}" stage). Re-evaluate the project with at least the "{2}" evaluation stage to access this member. + UE: This message is shown when the user reads a member (for example items or targets) that a partial evaluation did not compute. {0} is the member name, {1} is the ProjectEvaluationStage the project was evaluated to, {2} is the minimum ProjectEvaluationStage required to access the member. + Method {0} cannot be called with a collection containing null or empty target names. 无法使用包含 null 或空目标名称的集合调用方法 {0}。 diff --git a/src/Build/Resources/xlf/Strings.zh-Hant.xlf b/src/Build/Resources/xlf/Strings.zh-Hant.xlf index 3d72c08dace..43ffb92656f 100644 --- a/src/Build/Resources/xlf/Strings.zh-Hant.xlf +++ b/src/Build/Resources/xlf/Strings.zh-Hant.xlf @@ -719,6 +719,16 @@ MatchOnMetadata 無法在 <Target> 之外使用。 + + The project instance cannot be built because it was only partially evaluated (evaluation stopped after the "{0}" stage). A full evaluation is required to build a project. + The project instance cannot be built because it was only partially evaluated (evaluation stopped after the "{0}" stage). A full evaluation is required to build a project. + UE: This message is shown when the user tries to build a ProjectInstance produced by a partial evaluation. {0} is the ProjectEvaluationStage the instance was evaluated to. + + + The "{0}" member is not available because the project was only partially evaluated (evaluation stopped after the "{1}" stage). Re-evaluate the project with at least the "{2}" evaluation stage to access this member. + The "{0}" member is not available because the project was only partially evaluated (evaluation stopped after the "{1}" stage). Re-evaluate the project with at least the "{2}" evaluation stage to access this member. + UE: This message is shown when the user reads a member (for example items or targets) that a partial evaluation did not compute. {0} is the member name, {1} is the ProjectEvaluationStage the project was evaluated to, {2} is the minimum ProjectEvaluationStage required to access the member. + Method {0} cannot be called with a collection containing null or empty target names. 無法使用內含 null 或空白目標名稱的集合呼叫方法 {0}。