-
Notifications
You must be signed in to change notification settings - Fork 394
Basic example of consuming a code flow #8398
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
Draft
tmeschter
wants to merge
1
commit into
dotnet:main
Choose a base branch
from
tmeschter:220815-DataflowExample
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Draft
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
134 changes: 134 additions & 0 deletions
134
...ualStudio.ProjectSystem.Managed.VS/ProjectSystem/VS/Debug/LaunchProfileDataFlowExample.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,134 @@ | ||
// Licensed to the .NET Foundation under one or more agreements. The .NET Foundation licenses this file to you under the MIT license. See the LICENSE.md file in the project root for more information. | ||
|
||
using Microsoft.VisualStudio.ProjectSystem.Debug; | ||
using Microsoft.VisualStudio.ProjectSystem.Properties; | ||
|
||
namespace Microsoft.VisualStudio.ProjectSystem.VS.Debug; | ||
|
||
/// <remarks> | ||
/// An example of a very simple--but realistic and complete--use of data flows: consuming an existing data flow so | ||
/// we can detect changes to the launch settings, and update other parts of the system as a result. | ||
/// | ||
/// We're going to hook up to data flow exposed by ILaunchSettingsProvider.SourceBlock. The ILaunchSettingsProvider | ||
/// is only available when the project has the "LaunchProfiles" capability. While this particular capability does | ||
/// not tend to change while a project is loaded, in general capabilities can change and we need a way to react to | ||
/// that. Enter the IProjectDynamicLoadComponent. Implementations of this interface are "loaded" whenever their | ||
/// capabilities are satisfied, and "unloaded" whenever the capabilities are no longer satisfied. So here we export | ||
/// this as an IProjectDynamicLoadComponent, and use the AppliesTo attibute to tie it to the LaunchProfiles | ||
/// capability. | ||
/// | ||
/// Note we specify ExportContractNames.Scopes.UnconfiguredProject in the Export attribute because we expect/need | ||
/// one instance of this type per UnconfiguredProject. | ||
/// </remarks> | ||
[Export(ExportContractNames.Scopes.UnconfiguredProject, typeof(IProjectDynamicLoadComponent))] | ||
[AppliesTo(ProjectCapabilities.LaunchProfiles)] | ||
internal class LaunchProfileDataFlowExample : IProjectDynamicLoadComponent | ||
{ | ||
/// <remarks> | ||
/// We need the UnconfiguredProject for several reasons: | ||
/// 1. We need the full path to the project file so we can edit the properties in that file when the launch | ||
/// settings change. | ||
/// 2. It provides context for fault handling. If this type throws an exception while processing the data from | ||
/// the data flow, the project system can pop up a gold bar blaming the affected project and letting the | ||
/// user know that things may not work properly. | ||
/// 3. Importing an UnconfiguredProject in the constructor also guarantees we get a new instance of this type | ||
/// associated with each UnconfiguredProject (though this is a bit redundant as the other two imports would | ||
/// also cause this). | ||
/// </remarks> | ||
private readonly UnconfiguredProject _project; | ||
/// <remarks> | ||
/// We need the ILaunchSettingsProvider in order to hook up to ILaunchSettingsProvider.SourceBlock. | ||
/// </remarks> | ||
private readonly ILaunchSettingsProvider _launchSettingsProvider; | ||
/// <remarks> | ||
/// The IProjectPropertiesProvider is needed to update the project properties when the launch settings change. | ||
/// </remarks> | ||
private readonly IProjectPropertiesProvider _projectProperties; | ||
|
||
/// <remarks> | ||
/// Hooking up to a data flow source creates a link; disposing the link is how to break the connection to the data | ||
/// flow source when we no longer need it. | ||
/// </remarks> | ||
private IDisposable? _launchSettingsLink; | ||
/// <remarks> | ||
/// We need to store some state, namely, the previous command name/debug target. | ||
/// </remarks> | ||
private string? _previousCommandName = null; | ||
|
||
[ImportingConstructor] | ||
public LaunchProfileDataFlowExample( | ||
UnconfiguredProject unconfiguredProject, | ||
ILaunchSettingsProvider launchSettingsProvider, | ||
// There are multiple implementations of IProjectPropertiesProvider. The Import attribute here tells MEF to give | ||
// us the specific one named "ProjectFile". | ||
[Import(ContractNames.ProjectPropertyProviders.ProjectFile)] IProjectPropertiesProvider projectProperties) | ||
{ | ||
_project = unconfiguredProject; | ||
_launchSettingsProvider = launchSettingsProvider; | ||
_projectProperties = projectProperties; | ||
} | ||
|
||
/// <remarks> | ||
/// IProjectDynamicLoadComponent.LoadAsync is called every time the project satisifies the capabilities specified via | ||
/// AppliesTo. This is where we hook up to the data flow source. | ||
/// </remarks> | ||
public Task LoadAsync() | ||
{ | ||
// This is a defensive null check. It shouldn't be necessary, but prevents us from hooking up to the same data flow | ||
// multiple times and, more importantly, losing track of links that such that we can no longer break them. | ||
if (_launchSettingsLink is null) | ||
{ | ||
// Here we use the DataflowUtilities.LinkToAsyncAction extension method to connect the data flow to the | ||
// OnLaunchSettingsChangesAsync method, specifying the UnconfiguredProject as the "context" for the purposes of | ||
// error reporting. | ||
_launchSettingsLink = _launchSettingsProvider.SourceBlock | ||
.LinkToAsyncAction(OnLaunchSettingsChangedAsync, _project); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
This in turn depends on DataflowBlockFactory.CreateActionBlock. |
||
} | ||
|
||
return Task.CompletedTask; | ||
} | ||
|
||
/// <remarks> | ||
/// IProjectDynamicLoadComponent.UnloadAsync is called every time the project ceases to satisfy the capabilities specified | ||
/// via AppliesTo. This is where we break the link to the data flow source. | ||
/// </remarks> | ||
public Task UnloadAsync() | ||
{ | ||
// This is a defensive null check. It shouldn't be necessary, but prevents us from trying to call Dispose() on a null. | ||
if (_launchSettingsLink is not null) | ||
{ | ||
_launchSettingsLink.Dispose(); | ||
_launchSettingsLink = null; | ||
_previousCommandName = null; | ||
} | ||
|
||
return Task.CompletedTask; | ||
} | ||
|
||
/// <remarks> | ||
/// Called every time a new set of ILaunchSettings are pushed through the data flow pipeline. | ||
/// | ||
/// Note that setting a project property will cause us to enter the project system's write lock for the project while we | ||
/// are part of data flow processing. Mixing data flow and locking in this way is _generally_ a bad idea (in particular it | ||
/// is not safe to wait for data to come through a data flow while holding a read or write lock) but is probably OK here | ||
/// since the launch settings aren't actually covered by the read or write locks as they are not part of the project's | ||
/// MSBuild representation. If the data flow exposed MSBuild data, however, we would make the change to the project | ||
/// properties in a separate "fire-and-forget" task that dropped any ambient state (such the locks). | ||
/// </remarks> | ||
private async Task OnLaunchSettingsChangedAsync(ILaunchSettings arg) | ||
{ | ||
// If the "command name" associated with the active launch profile has changed... | ||
if (_previousCommandName is null | ||
|| !StringComparers.LaunchProfileCommandNames.Equals(arg?.ActiveProfile?.CommandName, _previousCommandName)) | ||
{ | ||
_previousCommandName = arg?.ActiveProfile?.CommandName; | ||
// ... then get the project properties for the project file (specifying null for itemType and item to indicate that | ||
// we want _project_ properties rather than _item_ metadata)... | ||
IProjectProperties properties = _projectProperties.GetProperties(_project.FullPath, itemType: null, item: null); | ||
|
||
// ... and then update a property value in the project file. | ||
|
||
await properties.SetPropertyValueAsync("CurrentCommandName", _previousCommandName ?? ""); | ||
} | ||
} | ||
} |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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.
ContractNames.ProjectPropertyProviders.ProjectFile
is defined in https://github.com/dotnet/project-system/blob/main/src/Microsoft.VisualStudio.ProjectSystem.Managed/ProjectSystem/ContractNames.cs.