-
Notifications
You must be signed in to change notification settings - Fork 392
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add new configuration/platform providers #1771
Add new configuration/platform providers #1771
Conversation
Is this ready to review? #Resolved |
Yup, this is the PR with the changes post CPS insertion. #Resolved |
build/Targets/References.targets
Outdated
@@ -89,7 +89,7 @@ | |||
<When Condition="'$(IncludeCPSReferences)' == 'true'"> | |||
<ItemGroup> | |||
|
|||
<PackageReference Include="Microsoft.VisualStudio.ProjectSystem.SDK" Version="15.0.737" PrivateAssets="None" /> | |||
<PackageReference Include="Microsoft.VisualStudio.ProjectSystem.SDK" Version="15.3.5-pre-g4382fbdb8f" PrivateAssets="None" /> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This needs a follow-up change to update the dependency in the Nuget and the change the release version to a pre release version for 15.3 Nugets #Resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This work is done #1810 #Resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll merge latest, this is most likely the merge conflict that is now showing. #Resolved
else | ||
{ | ||
// First value is the default one. | ||
var defaultValues = ImmutableArray.CreateBuilder<KeyValuePair<string, string>>(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This method returns the default value which should be just one value. We dont need to return IEnumerable<T>
, just T
should be enough. #ByDesign
/// </summary> | ||
/// <param name="unconfiguredProject">Unconfigured project.</param> | ||
/// <returns>Collection of key/value pairs for the defaults values for the configuration dimensions of this provider for given project.</returns> | ||
public virtual async Task<IEnumerable<KeyValuePair<string, string>>> GetDefaultValuesForDimensionsAsync(UnconfiguredProject unconfiguredProject) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we return a KeyValuePair
here? Instead, we can just return the value and if the consumer is interested then we can expose the dimension through a property on the Provider rather than baking that information everytime the call is made. Your call here. #Resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's an exsiting CPS interface so there's not much we can change there without making a changeo n the interface. I know this is extra confusing though I had to debug through it a few times to understand what it expects. The reason why it's an IEnumerable instead of a single value is that your provider can return multiple dimensions. So for example if instead of having one provider for config and one for platform we could have one that does both and you'd need to return at that point 2 values, one for the default config and one for the default platform. #Resolved
} | ||
|
||
/// <summary> | ||
/// Gets the project configuration dimensions for the given unconfigured project. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The description of this method is confusing. This method returns the value(s) for a given dimension in a project. The existing description, Gets the project configuration dimensions...
, and also the method name, seems to suggest that we are providing the different dimensions itself, For eg: Configurations
, Platforms
, rather than the value for those dimensions. #Resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same explanation as the other one regarding the name, and return values, this is part of the CPS interface (IProjectConfigurationDimensionsProvider). I used the xml doc comments for the interface however I can change ours to be more descriptive if that helps. BTW it does return both the dimension(s) and the value(s), it just happens that we return a single one though. #Resolved
/// </summary> | ||
/// <param name="unconfiguredProject">Unconfigured project.</param> | ||
/// <returns>Collection of key/value pairs for the current values for the configuration dimensions of this provider for given project.</returns> | ||
public virtual async Task<IEnumerable<KeyValuePair<string, IEnumerable<string>>>> GetProjectConfigurationDimensionsAsync(UnconfiguredProject unconfiguredProject) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The same comment as above, do we need KeyValuePair? #Resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See the explanation above. #Resolved
/// </summary> | ||
/// <param name="unconfiguredProject">Unconfigured project for which the configuration change.</param> | ||
/// <param name="configurationName">Name of the new configuration.</param> | ||
/// <returns>If the method succeeds, it returns S_OK. If it fails, it returns an error code.</returns> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This method is just returning Task
. Please update the description #Resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good catch, one of the initial implementations was using IVsCfgProvider and that was the doc for that interface. I'll go over all the comments for Add/Remove/Rename and make sure they are updated properly. #Resolved
/// </summary> | ||
/// <param name="unconfiguredProject">Unconfigured project for which the configuration change.</param> | ||
/// <param name="configurationName">Name of the deleted configuration.</param> | ||
/// <returns>If the method succeeds, it returns S_OK. If it fails, it returns an error code.</returns> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This method is just returning Task
. Please update the description #Resolved
StringBuilder value = new StringBuilder(); | ||
foreach (string propertyValue in GetPropertyValues(evaluatedPropertyValue, delimiter)) | ||
{ | ||
value.Append(propertyValue == oldValue ? newValue : propertyValue); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use string.Compare
#Resolved
/// <param name="newValue">New property value.</param> | ||
/// <param name="delimiter">Character used to delimit the property values.</param> | ||
/// <remarks> | ||
/// If the property is not present it will be added. This means that the evaluated property |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is the case of remark, where the old value not being found, handled? #Resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think you're asking about the value not the property itself right? Three options -
- Do a no-op, can't rename something that doesn't exist in the first place.
- Throw an error so that it can be handled upstream
- Treat it as an add and append the value at the end
From a truly generic point of view the second option makes more sense. I went with the first though because this is (currently) used in the limited scope of something that's guaranteed to exist (because the config manager dialog is blocking so you can't edit the file at the same time as you're doing edits). I don't feel strongly about it though and I'd be happy to change it to either of the two other options if that sounds better.
#Resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would prefer option 2 since this indicates data mismatch. Either CPS or us should throw en error when we encounter this.
/// <param name="unconfiguredProject">Unconfigured project for which the configuration change.</param> | ||
/// <param name="platformName">Name of the new platform.</param> | ||
/// <returns>If the method succeeds, it returns S_OK. If it fails, it returns an error code.</returns> | ||
private async Task OnPlatformAdded(UnconfiguredProject unconfiguredProject, string platformName) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Both OnPlatformAdded
and OnPlatformDeleted
can be abstracted away to the base class and used by Platform and Configuration Provider #Pending
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I left it like this with adding telemetry in mind but thinking about it some more that should still be easily doable even with them being in the base class. I'll address this comment when I add the telemetry calls. #Pending
{ | ||
using (var project = new MsBuildTempProjectFile()) | ||
{ | ||
MsBuildUtilities.GetOrAddProperty(project.Project, "MyProperty"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You also get hold of the ProjectPropertyElement
which you could inspect. #ByDesign
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That one was intentional. I wanted to validate the project itself rather than the individual element to circle back and make sure that the element did really get added. #ByDesign
MsBuildUtilities.GetOrAddProperty(project.Project, "MyProperty"); | ||
Assert.Equal(1, project.Project.Properties.Count); | ||
Assert.Equal(1, project.Project.PropertyGroups.Count); | ||
var group = project.Project.PropertyGroups.First(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: Line breaks after a bunch of asserts tells the user the rest of the test is testing a different component. Improves readability. #Resolved
|
||
namespace Microsoft.VisualStudio.ProjectSystem | ||
{ | ||
public class MsBuildTempProjectFile : IDisposable |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: MsBuildProjectFile
is fine too since this is already part of the UnitTests #Resolved
} | ||
|
||
[Fact] | ||
public async void ConfigurationProjectConfigurationDimensionProvider_OnDimensionValueChanged_Rename() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing case where the oldValue is not present. #Resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I didn't add it (or other negative tests for remove/rename) because those operations are currently a no-op and from a full integration point of view you can't get into that state (the provider is the source of truth so the data would have to get corrupted on the consumer before the request to rename comes back). That being said, if we do change the behavior of the utilities functions I'll go ahead and add tests for that. #Resolved
/// <param name="propertyValue">Value to remove from the property.</param> | ||
/// <param name="delimiter">Character used to delimit the property values.</param> | ||
/// <remarks> | ||
/// If the property is not present it will be added. This means that the evaluated property |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The remark seems wrong. #Resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
what is the expected behavior when we dont find the value that we are expected to find? This will represent the case where the data is corrupted. We should throw some exception or log the error to investigate why the data mismatch #Resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Look at my explanation on the other method. Same as there I don't feel strongly about it either way so I'm open to changing it to whatever makes the most sense. #Resolved
@@ -0,0 +1,362 @@ | |||
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: The test files end with Tests.cs
#Resolved
@RaulPerez1 Also a general question. When we get a rename event for Configuration, we modify the value in the Msbuild Construction Model. How would this change affect the conditions that were using the older Configuration value? Wouldn't those conditions break now that the old value does not exist? #Resolved |
@basoundr - Regarding the rename question - There's a LOT of code on the CPS side that handles conditions for all 3 operations (rename/delete/add) #Resolved |
@basoundr - I updated some of the documentation, specially around the CPS interface consumption. Let me know if that makes it clear or if it needs more explanations in there. Also there's a few open questions. |
namespace Microsoft.VisualStudio.ProjectSystem | ||
{ | ||
/// <summary> | ||
/// Utitlies class to manipulate MsBuild projects. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
typo
/// <param name="newValue">New property value.</param> | ||
/// <param name="delimiter">Character used to delimit the property values.</param> | ||
/// <remarks> | ||
/// If the property is not present it will be added. This means that the evaluated property |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would prefer option 2 since this indicates data mismatch. Either CPS or us should throw en error when we encounter this.
|
||
return project.Properties | ||
.Where(p => string.Equals(p.Name, propertyName, StringComparison.OrdinalIgnoreCase)) | ||
.FirstOrDefault(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The dimensions that we deal here are Platform and Configuration, which are usually not usually conditioned. But modifying a Msbuild Construction model does not ensure correctness. These are one-time operation or user-triggered operations, for which we can take a perf hit.
To ensure correctness you should be using this instead. This gives the XML element of the project property which is actually part of the Msbuild Evaluation Model rather than the Construction model. If we do this, then we dont have to worry whether the property is conditioned or not. It just works
@nguerrera - Can you take a look at the resource file change? Looks like VS made a bunch of changes on top of my 2 new strings, including some version change. Will that cause any issues? If so I can hand craft the changes instead of letting the designer do it for me. |
I took a brief look and this looks fine to me. @basoundr if you sign off, then @RaulPerez1 go ahead and checkin. If @davkean has any comments we can follow up afterwards. |
Creating a new PR since the original PR was pre dogfood changes and the merge is a mess.
This consumes the latest version of CPS which has the changes that allows us to define configuration and platform via Configurations and Platform properties, e.g.
Debug;Release
AnyCPU
#694
There are few workarounds to juggle around coordinating insertions in 3 repos (this, SDK and MsBuild):
SDK changes: dotnet/sdk#949
MsBuild changes: dotnet/msbuild#1879
This tracks the work to remove the workarounds once the above insertions have happened