KEMBAR78
Add support for updating properties via the Project Query API by tmeschter · Pull Request #6681 · dotnet/project-system · GitHub
Skip to content

Conversation

@tmeschter
Copy link
Contributor

@tmeschter tmeschter commented Oct 14, 2020

In addition to supporting querying for data, the Project Query API also specifies a set of actions you can take on various entities that are part of the API. In PR 277099 we defined two new actions that work on projects: setting the "evaluated" value of one of the project's properties, and setting the "unevaluated" value. The differences between the two are relatively small and not particularly interesting, so I'm not going to go into detail on those. Rather, I will focus on the code shared by both.

The ProjectActionProvider type plugs our implementation of these actions into the larger Project Query API system. The attributes on this type specify two critical pieces of information: which actions are supported (in this case SetEvaluatedUIPropertyValue and SetUnevaluatedUIPropertyValue) and which action targets are supported (in this case, the "project" entity indicated by ProjectType.TypeName). Most target entities will support multiple actions, and certain actions may be able to target multiple entities. For example, a "delete" action could apply to a file, a reference, or an entire project. The attributes allow us to specify which combination of targets and actions are handled by a particular provider.

An IQueryActionProvider such as ProjectActionProvider is responsible for returning an IQueryActionExecutor for a particular step of a query via the CreateQueryActionDataTransformer method. Here we create either a ProjectSetEvaluatedUIPropertyValueAction or a ProjectSetUnevaluatedUIPropertyValueAction instance. All of the interesting logic is in their shared base type, ProjectSetUIPropertyValueActionBase<T>. The entire purpose of this type is to extract interesting information from the Project Query API objects passed in, and the processing of this data is delegated to ProjectSetUIPropertyValueActionCore. This division means each type has fewer responsibilities, but the main purpose of the separation is to make it easier to test the core logic of updating property values without the tests having to deal with the Project Query API types.

Executing actions within a query takes place in three stages, corresponding to the methods in ProjectSetUIPropertyValueActionCore. The OnBeforeExecutingBatchAsync method is called once before the "body" of the query executes, and provides us a chance to do some setup. In this case, we retrieve and cache all of the IRules representing the property pages we are updating across all relevant configurations of all targets projects. Once the body of the query starts the execute, the Project Query API will be holding a lock on the project and we will no longer be able to retrieve these.

The ExecuteAsync method is called once for each targeted project. We pull out all of the relevant IRules from the cache, and apply the property update to each in turn.

The OnAfterExecutingBatch method is called once at the end of the query execution and provides an opportunity for clean-up. Here we just clean out the cache. This is probably unnecessary as we don't expect this instance to be re-used for other query executions, but this ensures we don't hold on to configuration-specific data longer than necessary.

Microsoft Reviewers: Open in CodeFlow

@tmeschter tmeschter requested a review from a team as a code owner October 14, 2020 21:00
Previous work made it possible to query managed projects for property page data via the Project Query API. This change adds support for the `ProjectModelActionNames.SetEvaluatedUIPropertyValue` and `ProjectModelActionNames.SetUnevaluatedUIPropertyValue` actions. Most of the logic is the same for both cases:

1. First, we find and cache the list of `IRule`s containing the properties to update in `OnBeforeExecutingBatchAsync`. We need to do this first because the action executor code in CPS will eventually take a lock, and at that point we won't be able to obtain the `IRule`s anymore. This is largely a matter of looping over the individual actions, figuring out which configurations of the project need to be updated, and getting the appropriate page rule from each.
2. Second, in `ReceiveResultAsync` we process each actual property update. We retrieve the cached rule, find the property, and set its value.
3. Finally, in `OnRequestProcessFinishedAsync` we clear out the list of cached rules.

The only place the implementations vary is in actually setting the property value, going through the evaluated or unevaluated property setters as appropriate.
Our Project Query API implementation frequently needs to access project configurations and rules. To speed access to these we cache them inside `PropertyPageQueryCache`. The operation of this type is rather mechanical, and as such is not very interesting from the point of view of testing the Project Query API implementation. At the same time it depends on multiple project services, and setting up mocks is cumbersome.

To simplify this, we previously abstracted away various uses of `PropertyPageQueryCache` with the `IPropertyPageQueryCache` interface. However, this still left a number of scenarios that were difficult to test as they depended on instantiating and using a `PropertyPageQueryCache`, and so they still had the same difficulties with adding mocks.

Here we go a step further and abstract away the creation of `IPropertyPageQueryCache` instances with the help of a new interface, `IPropertyPageQueryCacheProvider`. The "product" version of this is very simple: it just creates and returns a `PropertyPageQueryCache`. A future change will utilize this to make it easier to create mock `IPropertyPageQueryCache`s.
Extract out the core logic of setting a property within the matching configurations of a project from the traversal of the Project Query API types. `ProjectSetUIPropertyValueActionCore` has no dependencies on Project Query API types and is responsible for finding and updating the actual properties. On the other hand, `ProjectSetUIPropertyValueActionBase<T>` and its derived types are responsible for extracting the necessary data from the Project Query API types, and delegating to `ProjectSetUIPropertyValueActionCore`.

There are a couple of benefits to this: the types each have fewer responsibilities, and it is easier to write tests for the core logic as we don't need to wrap up the test data in the Project Query APIs types.
Also expand the set of helper methods for creating mocks.
@tmeschter tmeschter force-pushed the 200929-SupportUpdatingProperties branch from 9a7e2fb to 18622b8 Compare October 14, 2020 21:32
@drewnoakes
Copy link
Member

/azp run

@azure-pipelines
Copy link

Azure Pipelines successfully started running 2 pipeline(s).

@drewnoakes
Copy link
Member

Testing this via the UI and found that (in the local scenario) changes are not persisted to disk until I hit Ctrl+s. Not sure why this is exactly. It's late here so I'll investigate more tomorrow.

@tmeschter
Copy link
Contributor Author

Testing this via the UI and found that (in the local scenario) changes are not persisted to disk until I hit Ctrl+s. Not sure why this is exactly. It's late here so I'll investigate more tomorrow.

I don't think that's necessarily a problem--CPS sees the changes even if they haven't been persisted to disk.

@drewnoakes
Copy link
Member

drewnoakes commented Oct 16, 2020

The legacy property pages update the file on disk immediately. I'm unsure how saving the new editor actually causes the file on disk to change. There must be a link between the project file and the editor via the RDT. I've noticed that while the new editor is open, that attempts to edit the project file's XML result in the UI editor being focused.

Again this has nothing to do with this PR :)

@tmeschter tmeschter added Feature-Codespaces Running Visual Studio in a cloud environment Feature-Project-Properties-Designer The new project property pages which replace the legacy AppDesigner auto-merge Set on a PR to have msftbot merge once requirements are met. labels Oct 16, 2020
@ghost ghost merged commit 3b3e5a0 into dotnet:master Oct 16, 2020
@ghost ghost added this to the 16.9 milestone Oct 16, 2020
tmeschter added a commit to tmeschter/roslyn-project-system that referenced this pull request Apr 29, 2022
Related to [dotnet/maui dotnet#6681](dotnet/maui#6681).
Related to AB#1525249.

Currently MAUI includes platform-specific files are for every platform in the MSBuild evaluation, and then at build a target removes _all_ these items and then adds back the ones for the current platform.

This works for an actual build but screws up VS, as the Project System doesn't properly handle a `Compile` item that is present in evaluation but removed during a build. The end result is that the platform-specific files are not actually treated as platform-specific, leading to a number of user-visible issues:

1. Platform-specific files incorrectly show all target frameworks in the Nav Bar
2. Spurious errors in the Error List
3. Squiggles in the editor, unless the user changes the Nav Bar to the "correct" target framework
4. Possibly they will not see actual errors in their code (e.g. if you use a Windows-only API in an Android-specific file, but the Nav Bar is set to Windows, you won't get an squiggle about that)
5. The spurious errors block Hot Reload as the Language Service thinks the project is very broken and will not attempt to apply changes

With this change the Project System will now check items for special metadata indicating that they should be ignored. If an item is marked with `ExcludeFromCurrentConfiguration=true` the Project System will pretend it does not exist in the evaluation data for the current build configuration. And as the file no longer exists in the evaluation data, it no longer matters that the Project System does not properly handle the removal in the target.

See [dotnet/maui dotnet#6681](dotnet/maui#6681) for the related MAUI SDK changes that add the metadata to the appropriate items.
This pull request was closed.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

auto-merge Set on a PR to have msftbot merge once requirements are met. Feature-Codespaces Running Visual Studio in a cloud environment Feature-Project-Properties-Designer The new project property pages which replace the legacy AppDesigner

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants