diff --git a/documentation/specs/proposed/partial-evaluation.md b/documentation/specs/proposed/partial-evaluation.md index 3db0e1d53c6..69546e8e77e 100644 --- a/documentation/specs/proposed/partial-evaluation.md +++ b/documentation/specs/proposed/partial-evaluation.md @@ -55,13 +55,18 @@ public class ProjectOptions } ``` -The stage flows through the existing factory methods: +The stage flows through the `ProjectInstance` 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. +`ProjectInstance.EvaluationStage` reports the stage the instance was evaluated to. + +Partial evaluation is intentionally **not** supported on the mutable `Project` object model. `Project` +is an editable model that is cached in the `ProjectCollection`; a partially-evaluated `Project` would +either serve stale state from that cache or be silently upgraded to a full evaluation, discarding the +work the partial evaluation saved. The `Project` factory methods (`Project.FromFile`, +`Project.FromProjectRootElement`, `Project.FromXmlReader`) therefore throw `ArgumentException` when a +non-`Full` `EvaluationStage` is requested; use `ProjectInstance` for partial evaluation. Example: @@ -81,11 +86,10 @@ string value = instance.GetPropertyValue("PublishRelease"); // fast: only passes - **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). +- **Reading not-yet-computed state fails fast.** `ProjectInstance` members that expose state from a + later pass throw `InvalidOperationException` naming the member and the stage the instance reached. + Guarded members include `ItemDefinitions` (available from `ItemDefinitions` onward), `Items`, + `GetItems`, `Targets`, and `DefaultTargets`. - **A partial `ProjectInstance` cannot be built.** Constructing a `BuildRequestData` from a partial instance throws `InvalidOperationException`; a build requires a full evaluation. @@ -93,15 +97,11 @@ 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). +Partial evaluation applies only to `ProjectInstance`, which is an immutable evaluation snapshot that +is **not** stored in the `ProjectCollection` loaded-project cache. A partial `ProjectInstance` can +therefore never be returned in place of a fully-evaluated object for a later `Full` request, so there +is no cache-staleness or upgrade-in-place concern. This is a primary reason partial evaluation is +limited to `ProjectInstance`: the cached, mutable `Project` model cannot expose partial state safely. ## Relationship to `EvaluationContext` diff --git a/src/Build.UnitTests/Evaluation/PartialEvaluation_Tests.cs b/src/Build.UnitTests/Evaluation/PartialEvaluation_Tests.cs index eaf428ea218..099de1296f8 100644 --- a/src/Build.UnitTests/Evaluation/PartialEvaluation_Tests.cs +++ b/src/Build.UnitTests/Evaluation/PartialEvaluation_Tests.cs @@ -105,18 +105,34 @@ public void PropertiesStage_ExposesPropertiesButNotItemsOrTargets_ProjectInstanc } [Fact] - public void PropertiesStage_ExposesPropertiesButNotItemsOrTargets_Project() + public void ProjectFactories_RejectPartialEvaluationStage() { - Project project = Project.FromProjectRootElement(CreateRootElement(), OptionsFor(ProjectEvaluationStage.Properties)); + // Project only supports full evaluation. A partial stage passed through ProjectOptions + // must be rejected up front rather than silently ignored. + Should.Throw(() => + Project.FromProjectRootElement(CreateRootElement(), OptionsFor(ProjectEvaluationStage.Properties))); - project.EvaluationStage.ShouldBe(ProjectEvaluationStage.Properties); - project.GetPropertyValue("Derived").ShouldBe("Debug-x"); + Should.Throw(() => + Project.FromProjectRootElement(CreateRootElement(), OptionsFor(ProjectEvaluationStage.Items))); + } - Should.Throw(() => project.Items); - Should.Throw(() => project.ItemDefinitions); - Should.Throw(() => project.GetItems("Compile")); - Should.Throw(() => project.AllEvaluatedItems); - Should.Throw(() => project.Targets); + [Fact] + public void ProjectFactories_RejectNullOptions() + { + // The partial-stage guard is the first thing the factories do, so it must not dereference + // a null ProjectOptions; callers get the canonical ArgumentNullException instead. + Should.Throw(() => + Project.FromProjectRootElement(CreateRootElement(), null)); + } + + [Fact] + public void ProjectFactories_AllowFullEvaluationStage() + { + Project project = Project.FromProjectRootElement(CreateRootElement(), OptionsFor(ProjectEvaluationStage.Full)); + + project.GetPropertyValue("Derived").ShouldBe("Debug-x"); + project.GetItems("Compile").Count.ShouldBe(2); + project.Targets.ShouldContainKey("Build"); } [Fact] @@ -165,34 +181,6 @@ public void FullStage_IsDefault_AndExposesEverything() 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() { @@ -200,28 +188,5 @@ public void PartialProjectInstanceCannotBeBuilt() 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/Definition/Project.cs b/src/Build/Definition/Project.cs index 3665dfced9e..8c9bf0bffbc 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, ProjectEvaluationStage evaluationStage = ProjectEvaluationStage.Full) + EvaluationContext evaluationContext, IDirectoryCacheFactory directoryCacheFactory, bool interactive) { 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, evaluationStage); + defaultImplementation.Initialize(globalProperties, toolsVersion, subToolsetVersion, loadSettings, evaluationContext, interactive); } /// @@ -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, ProjectEvaluationStage evaluationStage = ProjectEvaluationStage.Full) + EvaluationContext evaluationContext, IDirectoryCacheFactory directoryCacheFactory, bool interactive) { 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, evaluationStage); + defaultImplementation.Initialize(globalProperties, toolsVersion, subToolsetVersion, loadSettings, evaluationContext, interactive); } /// @@ -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, ProjectEvaluationStage evaluationStage = ProjectEvaluationStage.Full) + EvaluationContext evaluationContext, IDirectoryCacheFactory directoryCacheFactory, bool interactive) { 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, evaluationStage); + defaultImplementation.Initialize(globalProperties, toolsVersion, subToolsetVersion, loadSettings, evaluationContext, interactive); } catch (Exception ex) when (!ExceptionHandling.IsCriticalException(ex)) { @@ -498,6 +498,7 @@ private Project(string projectFile, IDictionary globalProperties /// public static Project FromFile(string file, ProjectOptions options) { + ThrowIfPartialEvaluationRequested(options); return new Project( file, options.GlobalProperties, @@ -507,8 +508,7 @@ public static Project FromFile(string file, ProjectOptions options) options.LoadSettings, options.EvaluationContext, options.DirectoryCacheFactory, - options.Interactive, - options.EvaluationStage); + options.Interactive); } /// @@ -518,6 +518,7 @@ public static Project FromFile(string file, ProjectOptions options) /// The to use. public static Project FromProjectRootElement(ProjectRootElement rootElement, ProjectOptions options) { + ThrowIfPartialEvaluationRequested(options); return new Project( rootElement, options.GlobalProperties, @@ -527,8 +528,7 @@ public static Project FromProjectRootElement(ProjectRootElement rootElement, Pro options.LoadSettings, options.EvaluationContext, options.DirectoryCacheFactory, - options.Interactive, - options.EvaluationStage); + options.Interactive); } /// @@ -538,6 +538,7 @@ public static Project FromProjectRootElement(ProjectRootElement rootElement, Pro /// The to use. public static Project FromXmlReader(XmlReader reader, ProjectOptions options) { + ThrowIfPartialEvaluationRequested(options); return new Project( reader, options.GlobalProperties, @@ -547,8 +548,22 @@ public static Project FromXmlReader(XmlReader reader, ProjectOptions options) options.LoadSettings, options.EvaluationContext, options.DirectoryCacheFactory, - options.Interactive, - options.EvaluationStage); + options.Interactive); + } + + /// + /// only supports full evaluation. Throws if a caller requests a partial + /// evaluation via ; use + /// for partial (stop-after-pass) evaluation. + /// + private static void ThrowIfPartialEvaluationRequested(ProjectOptions options) + { + ArgumentNullException.ThrowIfNull(options); + + if (options.EvaluationStage != ProjectEvaluationStage.Full) + { + ErrorUtilities.ThrowArgument("OM_PartialEvaluationNotSupportedForProject", options.EvaluationStage); + } } /// @@ -843,16 +858,6 @@ 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. /// @@ -1886,14 +1891,6 @@ 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. @@ -2251,28 +2248,10 @@ 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. /// @@ -2281,7 +2260,6 @@ public override ICollection Items { get { - VerifyThrowEvaluationStageReached(ProjectEvaluationStage.Items, nameof(Items)); return new ReadOnlyCollection(_data.Items); } } @@ -2299,8 +2277,6 @@ public override ICollection ItemsIgnoringCondition [DebuggerStepThrough] get { - VerifyThrowEvaluationStageReached(ProjectEvaluationStage.Items, nameof(ItemsIgnoringCondition)); - if (!(_data.ShouldEvaluateForDesignTime && _data.CanEvaluateElementsWithFalseConditions)) { ErrorUtilities.ThrowInvalidOperation("OM_NotEvaluatedBecauseShouldEvaluateForDesignTimeIsFalse", nameof(ItemsIgnoringCondition)); @@ -2371,8 +2347,6 @@ public override IDictionary Targets [DebuggerStepThrough] get { - VerifyThrowEvaluationStageReached(ProjectEvaluationStage.Full, nameof(Targets)); - if (_data.Targets == null) { return ReadOnlyEmptyDictionary.Instance; @@ -2439,8 +2413,6 @@ public override ICollection AllEvaluatedItems { get { - VerifyThrowEvaluationStageReached(ProjectEvaluationStage.Items, nameof(AllEvaluatedItems)); - ICollection allEvaluatedItems = _data.AllEvaluatedItems; if (allEvaluatedItems == null) @@ -3183,7 +3155,6 @@ 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; } @@ -3198,7 +3169,6 @@ public override ICollection GetItems(string itemType) /// public override ICollection GetItemsIgnoringCondition(string itemType) { - VerifyThrowEvaluationStageReached(ProjectEvaluationStage.Items, nameof(GetItemsIgnoringCondition)); ICollection items = _data.ItemsIgnoringCondition[itemType]; return items; } @@ -3216,7 +3186,6 @@ public override ICollection GetItemsIgnoringCondition(string itemTy /// public override ICollection GetItemsByEvaluatedInclude(string evaluatedInclude) { - VerifyThrowEvaluationStageReached(ProjectEvaluationStage.Items, nameof(GetItemsByEvaluatedInclude)); ICollection items = _data.GetItemsByEvaluatedInclude(evaluatedInclude); return items; } @@ -3382,15 +3351,6 @@ 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); } @@ -3793,15 +3753,6 @@ 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); @@ -3831,8 +3782,7 @@ private void Reevaluate( evaluationContext.SdkResolverService, BuildEventContext.InvalidSubmissionId, evaluationContext, - _interactive, - _evaluationStage); + _interactive); Assumed.NotEqual(LastEvaluationId, BuildEventContext.InvalidEvaluationId, "Evaluation should produce an evaluation ID"); @@ -3865,7 +3815,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, ProjectEvaluationStage evaluationStage = ProjectEvaluationStage.Full) + internal void Initialize(IDictionary globalProperties, string toolsVersion, string subToolsetVersion, ProjectLoadSettings loadSettings, EvaluationContext evaluationContext, bool interactive) { Xml.MarkAsExplicitlyLoaded(); @@ -3901,13 +3851,9 @@ 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"); - // 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 326db40e416..9bb0a95a5ab 100644 --- a/src/Build/Definition/ProjectCollection.cs +++ b/src/Build/Definition/ProjectCollection.cs @@ -1323,16 +1323,6 @@ 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); diff --git a/src/Build/Definition/ProjectOptions.cs b/src/Build/Definition/ProjectOptions.cs index 8193bb56321..f080a380abc 100644 --- a/src/Build/Definition/ProjectOptions.cs +++ b/src/Build/Definition/ProjectOptions.cs @@ -59,6 +59,10 @@ public class ProjectOptions /// /// The controlling how far evaluation should proceed. /// Defaults to (a complete evaluation). + /// A non- (partial) stage is only honored when creating a + /// ; passing a partial stage to a factory + /// (for example ) throws + /// . /// public ProjectEvaluationStage EvaluationStage { diff --git a/src/Build/Resources/Strings.resx b/src/Build/Resources/Strings.resx index 4b44ec202d2..d3bcbf59ae5 100644 --- a/src/Build/Resources/Strings.resx +++ b/src/Build/Resources/Strings.resx @@ -1595,6 +1595,10 @@ Utilization: {0} Average Utilization: {1:###.0} 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. + + Partial evaluation (the "{0}" evaluation stage) is not supported when creating a Project. Use ProjectInstance to perform a partial evaluation, or use the Full evaluation stage to create a Project. + UE: This message is shown when a partial (non-Full) ProjectEvaluationStage is passed to a Project factory method such as Project.FromFile. {0} is the requested ProjectEvaluationStage. + 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 36291f13b68..61138542ee3 100644 --- a/src/Build/Resources/xlf/Strings.cs.xlf +++ b/src/Build/Resources/xlf/Strings.cs.xlf @@ -729,6 +729,11 @@ 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. + + Partial evaluation (the "{0}" evaluation stage) is not supported when creating a Project. Use ProjectInstance to perform a partial evaluation, or use the Full evaluation stage to create a Project. + Partial evaluation (the "{0}" evaluation stage) is not supported when creating a Project. Use ProjectInstance to perform a partial evaluation, or use the Full evaluation stage to create a Project. + UE: This message is shown when a partial (non-Full) ProjectEvaluationStage is passed to a Project factory method such as Project.FromFile. {0} is the requested ProjectEvaluationStage. + 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. @@ -3316,4 +3321,4 @@ Využití: Průměrné využití {0}: {1:###.0} - \ No newline at end of file + diff --git a/src/Build/Resources/xlf/Strings.de.xlf b/src/Build/Resources/xlf/Strings.de.xlf index 6e1fffc81ee..04a83e43a6e 100644 --- a/src/Build/Resources/xlf/Strings.de.xlf +++ b/src/Build/Resources/xlf/Strings.de.xlf @@ -729,6 +729,11 @@ 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. + + Partial evaluation (the "{0}" evaluation stage) is not supported when creating a Project. Use ProjectInstance to perform a partial evaluation, or use the Full evaluation stage to create a Project. + Partial evaluation (the "{0}" evaluation stage) is not supported when creating a Project. Use ProjectInstance to perform a partial evaluation, or use the Full evaluation stage to create a Project. + UE: This message is shown when a partial (non-Full) ProjectEvaluationStage is passed to a Project factory method such as Project.FromFile. {0} is the requested ProjectEvaluationStage. + 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. @@ -3316,4 +3321,4 @@ Auslastung: {0} Durchschnittliche Auslastung: {1:###.0} - \ No newline at end of file + diff --git a/src/Build/Resources/xlf/Strings.es.xlf b/src/Build/Resources/xlf/Strings.es.xlf index 6002b809c5b..fc1d68ed20b 100644 --- a/src/Build/Resources/xlf/Strings.es.xlf +++ b/src/Build/Resources/xlf/Strings.es.xlf @@ -729,6 +729,11 @@ 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. + + Partial evaluation (the "{0}" evaluation stage) is not supported when creating a Project. Use ProjectInstance to perform a partial evaluation, or use the Full evaluation stage to create a Project. + Partial evaluation (the "{0}" evaluation stage) is not supported when creating a Project. Use ProjectInstance to perform a partial evaluation, or use the Full evaluation stage to create a Project. + UE: This message is shown when a partial (non-Full) ProjectEvaluationStage is passed to a Project factory method such as Project.FromFile. {0} is the requested ProjectEvaluationStage. + 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. @@ -3316,4 +3321,4 @@ Utilización: Utilización media de {0}: {1:###.0} - \ No newline at end of file + diff --git a/src/Build/Resources/xlf/Strings.fr.xlf b/src/Build/Resources/xlf/Strings.fr.xlf index 3b345dc71bb..139825b8520 100644 --- a/src/Build/Resources/xlf/Strings.fr.xlf +++ b/src/Build/Resources/xlf/Strings.fr.xlf @@ -729,6 +729,11 @@ 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. + + Partial evaluation (the "{0}" evaluation stage) is not supported when creating a Project. Use ProjectInstance to perform a partial evaluation, or use the Full evaluation stage to create a Project. + Partial evaluation (the "{0}" evaluation stage) is not supported when creating a Project. Use ProjectInstance to perform a partial evaluation, or use the Full evaluation stage to create a Project. + UE: This message is shown when a partial (non-Full) ProjectEvaluationStage is passed to a Project factory method such as Project.FromFile. {0} is the requested ProjectEvaluationStage. + 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. @@ -3316,4 +3321,4 @@ Utilisation : {0} Utilisation moyenne : {1:###.0} - \ No newline at end of file + diff --git a/src/Build/Resources/xlf/Strings.it.xlf b/src/Build/Resources/xlf/Strings.it.xlf index 5ebab6990fb..388f19b78a0 100644 --- a/src/Build/Resources/xlf/Strings.it.xlf +++ b/src/Build/Resources/xlf/Strings.it.xlf @@ -729,6 +729,11 @@ 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. + + Partial evaluation (the "{0}" evaluation stage) is not supported when creating a Project. Use ProjectInstance to perform a partial evaluation, or use the Full evaluation stage to create a Project. + Partial evaluation (the "{0}" evaluation stage) is not supported when creating a Project. Use ProjectInstance to perform a partial evaluation, or use the Full evaluation stage to create a Project. + UE: This message is shown when a partial (non-Full) ProjectEvaluationStage is passed to a Project factory method such as Project.FromFile. {0} is the requested ProjectEvaluationStage. + 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. @@ -3316,4 +3321,4 @@ Utilizzo: {0} Utilizzo medio: {1:###.0} - \ No newline at end of file + diff --git a/src/Build/Resources/xlf/Strings.ja.xlf b/src/Build/Resources/xlf/Strings.ja.xlf index ce2837fe8ec..5f90a6860c2 100644 --- a/src/Build/Resources/xlf/Strings.ja.xlf +++ b/src/Build/Resources/xlf/Strings.ja.xlf @@ -729,6 +729,11 @@ 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. + + Partial evaluation (the "{0}" evaluation stage) is not supported when creating a Project. Use ProjectInstance to perform a partial evaluation, or use the Full evaluation stage to create a Project. + Partial evaluation (the "{0}" evaluation stage) is not supported when creating a Project. Use ProjectInstance to perform a partial evaluation, or use the Full evaluation stage to create a Project. + UE: This message is shown when a partial (non-Full) ProjectEvaluationStage is passed to a Project factory method such as Project.FromFile. {0} is the requested ProjectEvaluationStage. + Method {0} cannot be called with a collection containing null or empty target names. Null または空のターゲット名を含むコレクションを指定してメソッド {0} を呼び出すことはできません。 @@ -3316,4 +3321,4 @@ Utilization: {0} Average Utilization: {1:###.0} - \ No newline at end of file + diff --git a/src/Build/Resources/xlf/Strings.ko.xlf b/src/Build/Resources/xlf/Strings.ko.xlf index fa72703a507..b7ad3ff1219 100644 --- a/src/Build/Resources/xlf/Strings.ko.xlf +++ b/src/Build/Resources/xlf/Strings.ko.xlf @@ -729,6 +729,11 @@ 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. + + Partial evaluation (the "{0}" evaluation stage) is not supported when creating a Project. Use ProjectInstance to perform a partial evaluation, or use the Full evaluation stage to create a Project. + Partial evaluation (the "{0}" evaluation stage) is not supported when creating a Project. Use ProjectInstance to perform a partial evaluation, or use the Full evaluation stage to create a Project. + UE: This message is shown when a partial (non-Full) ProjectEvaluationStage is passed to a Project factory method such as Project.FromFile. {0} is the requested ProjectEvaluationStage. + Method {0} cannot be called with a collection containing null or empty target names. null 또는 빈 대상 이름을 포함하는 컬렉션을 사용하여 {0} 메서드를 호출할 수 없습니다. @@ -3316,4 +3321,4 @@ Utilization: {0} Average Utilization: {1:###.0} - \ No newline at end of file + diff --git a/src/Build/Resources/xlf/Strings.pl.xlf b/src/Build/Resources/xlf/Strings.pl.xlf index 43bd732de9c..6f67aafc4a9 100644 --- a/src/Build/Resources/xlf/Strings.pl.xlf +++ b/src/Build/Resources/xlf/Strings.pl.xlf @@ -729,6 +729,11 @@ 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. + + Partial evaluation (the "{0}" evaluation stage) is not supported when creating a Project. Use ProjectInstance to perform a partial evaluation, or use the Full evaluation stage to create a Project. + Partial evaluation (the "{0}" evaluation stage) is not supported when creating a Project. Use ProjectInstance to perform a partial evaluation, or use the Full evaluation stage to create a Project. + UE: This message is shown when a partial (non-Full) ProjectEvaluationStage is passed to a Project factory method such as Project.FromFile. {0} is the requested ProjectEvaluationStage. + 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. @@ -3316,4 +3321,4 @@ Wykorzystanie: Średnie wykorzystanie {0}: {1:###.0} - \ No newline at end of file + diff --git a/src/Build/Resources/xlf/Strings.pt-BR.xlf b/src/Build/Resources/xlf/Strings.pt-BR.xlf index 652a7aa3770..c3af0f60965 100644 --- a/src/Build/Resources/xlf/Strings.pt-BR.xlf +++ b/src/Build/Resources/xlf/Strings.pt-BR.xlf @@ -729,6 +729,11 @@ 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. + + Partial evaluation (the "{0}" evaluation stage) is not supported when creating a Project. Use ProjectInstance to perform a partial evaluation, or use the Full evaluation stage to create a Project. + Partial evaluation (the "{0}" evaluation stage) is not supported when creating a Project. Use ProjectInstance to perform a partial evaluation, or use the Full evaluation stage to create a Project. + UE: This message is shown when a partial (non-Full) ProjectEvaluationStage is passed to a Project factory method such as Project.FromFile. {0} is the requested ProjectEvaluationStage. + 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. @@ -3316,4 +3321,4 @@ Utilização: {0} Utilização Média: {1:###.0} - \ No newline at end of file + diff --git a/src/Build/Resources/xlf/Strings.ru.xlf b/src/Build/Resources/xlf/Strings.ru.xlf index 7df976ebd2e..f583635673c 100644 --- a/src/Build/Resources/xlf/Strings.ru.xlf +++ b/src/Build/Resources/xlf/Strings.ru.xlf @@ -729,6 +729,11 @@ 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. + + Partial evaluation (the "{0}" evaluation stage) is not supported when creating a Project. Use ProjectInstance to perform a partial evaluation, or use the Full evaluation stage to create a Project. + Partial evaluation (the "{0}" evaluation stage) is not supported when creating a Project. Use ProjectInstance to perform a partial evaluation, or use the Full evaluation stage to create a Project. + UE: This message is shown when a partial (non-Full) ProjectEvaluationStage is passed to a Project factory method such as Project.FromFile. {0} is the requested ProjectEvaluationStage. + Method {0} cannot be called with a collection containing null or empty target names. Метод {0} не может быть вызван с коллекцией, содержащей целевые имена, которые пусты или равны NULL. @@ -3316,4 +3321,4 @@ Utilization: {0} Average Utilization: {1:###.0} - \ No newline at end of file + diff --git a/src/Build/Resources/xlf/Strings.tr.xlf b/src/Build/Resources/xlf/Strings.tr.xlf index ee4c6ea2048..7c54a41114e 100644 --- a/src/Build/Resources/xlf/Strings.tr.xlf +++ b/src/Build/Resources/xlf/Strings.tr.xlf @@ -729,6 +729,11 @@ 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. + + Partial evaluation (the "{0}" evaluation stage) is not supported when creating a Project. Use ProjectInstance to perform a partial evaluation, or use the Full evaluation stage to create a Project. + Partial evaluation (the "{0}" evaluation stage) is not supported when creating a Project. Use ProjectInstance to perform a partial evaluation, or use the Full evaluation stage to create a Project. + UE: This message is shown when a partial (non-Full) ProjectEvaluationStage is passed to a Project factory method such as Project.FromFile. {0} is the requested ProjectEvaluationStage. + 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. @@ -3316,4 +3321,4 @@ Kullanım: {0} Ortalama Kullanım: {1:###.0} - \ No newline at end of file + diff --git a/src/Build/Resources/xlf/Strings.zh-Hans.xlf b/src/Build/Resources/xlf/Strings.zh-Hans.xlf index 4718c74cf1f..7fbec3bc7fd 100644 --- a/src/Build/Resources/xlf/Strings.zh-Hans.xlf +++ b/src/Build/Resources/xlf/Strings.zh-Hans.xlf @@ -729,6 +729,11 @@ 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. + + Partial evaluation (the "{0}" evaluation stage) is not supported when creating a Project. Use ProjectInstance to perform a partial evaluation, or use the Full evaluation stage to create a Project. + Partial evaluation (the "{0}" evaluation stage) is not supported when creating a Project. Use ProjectInstance to perform a partial evaluation, or use the Full evaluation stage to create a Project. + UE: This message is shown when a partial (non-Full) ProjectEvaluationStage is passed to a Project factory method such as Project.FromFile. {0} is the requested ProjectEvaluationStage. + Method {0} cannot be called with a collection containing null or empty target names. 无法使用包含 null 或空目标名称的集合调用方法 {0}。 @@ -3316,4 +3321,4 @@ Utilization: {0} Average Utilization: {1:###.0} - \ No newline at end of file + diff --git a/src/Build/Resources/xlf/Strings.zh-Hant.xlf b/src/Build/Resources/xlf/Strings.zh-Hant.xlf index 0b640b484a4..98779bb2a10 100644 --- a/src/Build/Resources/xlf/Strings.zh-Hant.xlf +++ b/src/Build/Resources/xlf/Strings.zh-Hant.xlf @@ -729,6 +729,11 @@ 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. + + Partial evaluation (the "{0}" evaluation stage) is not supported when creating a Project. Use ProjectInstance to perform a partial evaluation, or use the Full evaluation stage to create a Project. + Partial evaluation (the "{0}" evaluation stage) is not supported when creating a Project. Use ProjectInstance to perform a partial evaluation, or use the Full evaluation stage to create a Project. + UE: This message is shown when a partial (non-Full) ProjectEvaluationStage is passed to a Project factory method such as Project.FromFile. {0} is the requested ProjectEvaluationStage. + Method {0} cannot be called with a collection containing null or empty target names. 無法使用內含 null 或空白目標名稱的集合呼叫方法 {0}。 @@ -3316,4 +3321,4 @@ Utilization: {0} Average Utilization: {1:###.0} - \ No newline at end of file + diff --git a/src/MSBuild.Benchmarks/PartialEvaluationBenchmark.cs b/src/MSBuild.Benchmarks/PartialEvaluationBenchmark.cs index 8d5345ad94b..51bdd2f4a1b 100644 --- a/src/MSBuild.Benchmarks/PartialEvaluationBenchmark.cs +++ b/src/MSBuild.Benchmarks/PartialEvaluationBenchmark.cs @@ -5,6 +5,7 @@ using BenchmarkDotNet.Attributes; using Microsoft.Build.Definition; using Microsoft.Build.Evaluation; +using Microsoft.Build.Execution; namespace MSBuild.Benchmarks; @@ -16,7 +17,7 @@ namespace MSBuild.Benchmarks; /// /// /// Each invocation mirrors the CLI path in XMake.cs: a fresh -/// is created, the project is loaded via , and +/// is created, the project is loaded via , and /// a single property value is read. The project XML is written to disk once in /// and re-parsed on every invocation (as the CLI does), so the reported /// delta reflects the evaluation passes that partial evaluation skips, not parsing. @@ -106,7 +107,7 @@ private string EvaluateAndReadProperty(ProjectEvaluationStage stage) // A fresh collection per invocation mirrors the CLI and avoids cross-iteration caching so the // project XML is re-parsed and re-evaluated every time, exactly as `msbuild -getProperty` does. using ProjectCollection collection = new(); - Project project = Project.FromFile(_projectPath, new ProjectOptions + ProjectInstance project = ProjectInstance.FromFile(_projectPath, new ProjectOptions { ProjectCollection = collection, EvaluationStage = stage, @@ -118,7 +119,7 @@ private string EvaluateAndReadProperty(ProjectEvaluationStage stage) private string EvaluateAndReadItems(ProjectEvaluationStage stage) { using ProjectCollection collection = new(); - Project project = Project.FromFile(_projectPath, new ProjectOptions + ProjectInstance project = ProjectInstance.FromFile(_projectPath, new ProjectOptions { ProjectCollection = collection, EvaluationStage = stage, diff --git a/src/MSBuild/JsonOutputFormatter.cs b/src/MSBuild/JsonOutputFormatter.cs index c5e4b396e22..b09f6e5ed83 100644 --- a/src/MSBuild/JsonOutputFormatter.cs +++ b/src/MSBuild/JsonOutputFormatter.cs @@ -4,7 +4,6 @@ using System; using System.Text.Json; using System.Text.Json.Nodes; -using Microsoft.Build.Evaluation; using Microsoft.Build.Execution; using Microsoft.Build.Framework; using Microsoft.Build.Shared; @@ -79,47 +78,6 @@ internal void AddItemInstancesInJsonFormat(string[] itemNames, ProjectInstance p _topLevelNode["Items"] = itemsNode; } - internal void AddItemsInJsonFormat(string[] itemNames, Project project) - { - if (itemNames.Length == 0) - { - return; - } - - Assumed.Null(_topLevelNode["Items"], "Should not add multiple lists of items to the json format."); - - JsonObject itemsNode = new(); - foreach (string itemName in itemNames) - { - JsonArray itemArray = new(); - foreach (ProjectItem item in project.GetItems(itemName)) - { - JsonObject jsonItem = new(); - jsonItem["Identity"] = item.GetMetadataValue("Identity"); - foreach (ProjectMetadata metadatum in item.Metadata) - { - jsonItem[metadatum.Name] = metadatum.EvaluatedValue; - } - - foreach (string metadatumName in ItemSpecModifiers.All) - { - if (metadatumName.Equals("Identity")) - { - continue; - } - - jsonItem[metadatumName] = TryGetMetadataValue(item, metadatumName); - } - - itemArray.Add(jsonItem); - } - - itemsNode[itemName] = itemArray; - } - - _topLevelNode["Items"] = itemsNode; - } - internal void AddTargetResultsInJsonFormat(string[] targetNames, BuildResult result) { if (targetNames.Length == 0) @@ -197,24 +155,5 @@ private static string TryGetMetadataValue(ProjectItemInstance item, string metad return string.Empty; } } - - /// - /// Attempts to get metadata value from a ProjectItem. If the metadata is a built-in metadata - /// (like FullPath, Directory, etc.) and the item spec contains illegal path characters, - /// this will catch the InvalidOperationException and return an empty string. - /// - private static string TryGetMetadataValue(ProjectItem item, string metadataName) - { - try - { - return item.GetMetadataValue(metadataName); - } - catch (InvalidOperationException) - { - // Built-in metadata like FullPath, Directory, etc. require path computation. - // If the item spec contains illegal path characters, return empty string. - return string.Empty; - } - } } } diff --git a/src/MSBuild/XMake.cs b/src/MSBuild/XMake.cs index 4aeef850812..cb41439a5ca 100644 --- a/src/MSBuild/XMake.cs +++ b/src/MSBuild/XMake.cs @@ -1006,12 +1006,15 @@ internal static ExitType Execute( // Properties pass; items require the Items pass. This skips the later passes // (using-tasks and target registration) entirely. Gated behind change wave 18.10 // so the historical full-evaluation behavior can be restored if needed. + // A ProjectInstance (an uncached evaluation snapshot) is used rather than a Project + // because this is a read-only "evaluate, read, discard" scenario and partial + // evaluation is only supported on ProjectInstance. ProjectEvaluationStage evaluationStage = ChangeWaves.AreFeaturesEnabled(ChangeWaves.Wave18_10) ? (getItem.Length == 0 ? ProjectEvaluationStage.Properties : ProjectEvaluationStage.Items) : ProjectEvaluationStage.Full; - Project project = Project.FromFile(projectFile, new ProjectOptions + ProjectInstance project = ProjectInstance.FromFile(projectFile, new ProjectOptions { ProjectCollection = collection, GlobalProperties = globalProperties, @@ -1033,8 +1036,17 @@ internal static ExitType Execute( collection.LogBuildFinishedEvent(exitType == ExitType.Success); } } - catch (InvalidProjectFileException) + catch (InvalidProjectFileException ex) { + // ProjectInstance evaluation logs semantic evaluation errors through the collection's + // loggers, but a pre-evaluation failure (for example malformed project XML) throws + // before any logging occurs. Surface the latter in canonical format so the CLI still + // reports the project load error (for example MSB4025) rather than failing silently. + if (!ex.HasBeenLogged) + { + Console.WriteLine($"MSBUILD : error {ex.ErrorCode}: {ex.Message}"); + } + exitType = ExitType.BuildError; } } @@ -1315,7 +1327,7 @@ private static void RecordCrashTelemetry(Exception exception, ExitType exitType, isStandaloneExecution: !s_isNodeMode); } - private static ExitType OutputPropertiesAfterEvaluation(string[] getProperty, string[] getItem, Project project, TextWriter outputStream) + private static ExitType OutputPropertiesAfterEvaluation(string[] getProperty, string[] getItem, ProjectInstance project, TextWriter outputStream) { // Special case if the user requests exactly one property: skip json formatting if (getProperty.Length == 1 && getItem.Length == 0) @@ -1326,7 +1338,7 @@ private static ExitType OutputPropertiesAfterEvaluation(string[] getProperty, st { JsonOutputFormatter jsonOutputFormatter = new(); jsonOutputFormatter.AddPropertiesInJsonFormat(getProperty, property => project.GetPropertyValue(property)); - jsonOutputFormatter.AddItemsInJsonFormat(getItem, project); + jsonOutputFormatter.AddItemInstancesInJsonFormat(getItem, project); outputStream.WriteLine(jsonOutputFormatter.ToString()); }