-
Notifications
You must be signed in to change notification settings - Fork 256
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 component debugger #726
Merged
Merged
Changes from 6 commits
Commits
Show all changes
12 commits
Select commit
Hold shift + click to select a range
ce5ccd6
Add component debugger
chsienki b079869
PR Feedback
chsienki 5ef7fee
Address PROTOTYPE: comments:
chsienki f2ddb61
Add package source
chsienki c86f8bd
Use async lazy to get compiler root from the UI thread
chsienki 92e3b89
NuGoop
chsienki ffd65a0
Add cap provider for VB
chsienki f78a1b7
Make capability provider dataflow based
chsienki 492c07c
Don't warn on downgrade
chsienki 65f8b41
cries in nuget
chsienki 2af0f11
nuget harder
chsienki 4dcffa0
Nuget 4.0
chsienki 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
This file contains 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
This file contains 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
This file contains 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
47 changes: 47 additions & 0 deletions
47
src/VisualStudio.Roslyn.SDK/ComponentDebugger/CapabilityProvider.cs
This file contains 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,47 @@ | ||
// 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 file in the project root for more information. | ||
|
||
using System.Collections.Immutable; | ||
using System.ComponentModel.Composition; | ||
using System.Threading; | ||
using System.Threading.Tasks; | ||
using Microsoft.VisualStudio.ProjectSystem; | ||
|
||
namespace Roslyn.ComponentDebugger | ||
{ | ||
[Export(ExportContractNames.Scopes.ConfiguredProject, typeof(IProjectCapabilitiesProvider))] | ||
[AppliesTo(ProjectCapabilities.CSharp)] | ||
public class CapabilityProvider : ConfiguredProjectCapabilitiesProviderBase | ||
{ | ||
[ImportingConstructor] | ||
[System.Obsolete("This exported object must be obtained through the MEF export provider.", error: true)] | ||
public CapabilityProvider(ConfiguredProject configuredProject) | ||
: base(nameof(CapabilityProvider), configuredProject) | ||
{ | ||
} | ||
|
||
protected override async Task<ImmutableHashSet<string>> GetCapabilitiesAsync(CancellationToken cancellationToken) | ||
{ | ||
// an alternative design could be to have 'IsRoslynComponent' just define the <Capability... directly in the managed.core targets | ||
// but that would require a specific roslyn version to work, this allows it to be backwards compatible with older SDKs | ||
var caps = Empty.CapabilitiesSet; | ||
if (await IsRoslynComponentAsync(this.ConfiguredProject, cancellationToken).ConfigureAwait(false)) | ||
{ | ||
caps = caps.Add(Constants.RoslynComponentCapability); | ||
} | ||
return caps; | ||
} | ||
|
||
private static Task<bool> IsRoslynComponentAsync(ConfiguredProject configuredProject, CancellationToken token = default) | ||
=> configuredProject.Services.ProjectLockService.ReadLockAsync( | ||
async access => | ||
{ | ||
var project = await access.GetProjectAsync(configuredProject).ConfigureAwait(false); | ||
var isRoslynComponentProperty = project.GetProperty(Constants.RoslynComponentPropertyName); | ||
var isComponent = string.Compare(isRoslynComponentProperty?.EvaluatedValue.Trim(), "true", System.StringComparison.OrdinalIgnoreCase) == 0; | ||
return isComponent; | ||
}, | ||
token); | ||
} | ||
} |
17 changes: 17 additions & 0 deletions
17
src/VisualStudio.Roslyn.SDK/ComponentDebugger/Constants.cs
This file contains 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,17 @@ | ||
// 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 file in the project root for more information. | ||
|
||
namespace Roslyn.ComponentDebugger | ||
{ | ||
internal static class Constants | ||
{ | ||
public const string RoslynComponentPropertyName = "IsRoslynComponent"; | ||
|
||
public const string RoslynComponentCapability = "RoslynComponent"; | ||
|
||
public const string CommandName = "DebugRoslynComponent"; | ||
|
||
public const string TargetProjectPropertyName = "targetProject"; | ||
} | ||
} |
109 changes: 109 additions & 0 deletions
109
src/VisualStudio.Roslyn.SDK/ComponentDebugger/DebugProfileProvider.cs
This file contains 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,109 @@ | ||
// 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 file in the project root for more information. | ||
|
||
using System; | ||
using System.Collections.Generic; | ||
using System.Collections.Immutable; | ||
using System.ComponentModel.Composition; | ||
using System.IO; | ||
using System.Linq; | ||
using System.Threading.Tasks; | ||
using Microsoft.VisualStudio.ProjectSystem; | ||
using Microsoft.VisualStudio.ProjectSystem.Debug; | ||
using Microsoft.VisualStudio.ProjectSystem.VS.Debug; | ||
using Microsoft.VisualStudio.Shell; | ||
using Microsoft.VisualStudio.Shell.Interop; | ||
using Microsoft.VisualStudio.Threading; | ||
using Task = System.Threading.Tasks.Task; | ||
|
||
namespace Roslyn.ComponentDebugger | ||
{ | ||
[Export(typeof(IDebugProfileLaunchTargetsProvider))] | ||
[AppliesTo(Constants.RoslynComponentCapability)] | ||
public class DebugProfileProvider : IDebugProfileLaunchTargetsProvider | ||
{ | ||
private readonly ConfiguredProject _configuredProject; | ||
private readonly IDebugTokenReplacer _tokenReplacer; | ||
private readonly AsyncLazy<string> _compilerRoot; | ||
|
||
[ImportingConstructor] | ||
[Obsolete("This exported object must be obtained through the MEF export provider.", error: true)] | ||
public DebugProfileProvider(ConfiguredProject configuredProject, IDebugTokenReplacer tokenReplacer, SVsServiceProvider? serviceProvider) | ||
{ | ||
_configuredProject = configuredProject; | ||
_tokenReplacer = tokenReplacer; | ||
_compilerRoot = new AsyncLazy<string>(() => GetCompilerRootAsync(serviceProvider), ThreadHelper.JoinableTaskFactory); | ||
} | ||
chsienki marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
public Task OnAfterLaunchAsync(DebugLaunchOptions launchOptions, ILaunchProfile profile) => Task.CompletedTask; | ||
|
||
public Task OnBeforeLaunchAsync(DebugLaunchOptions launchOptions, ILaunchProfile profile) => Task.CompletedTask; | ||
|
||
public bool SupportsProfile(ILaunchProfile? profile) => Constants.CommandName.Equals(profile?.CommandName, StringComparison.OrdinalIgnoreCase); | ||
chsienki marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
public async Task<IReadOnlyList<IDebugLaunchSettings>> QueryDebugTargetsAsync(DebugLaunchOptions launchOptions, ILaunchProfile? profile) | ||
{ | ||
// set up the managed (net fx) debugger to start a process | ||
// https://github.com/dotnet/roslyn-sdk/issues/729 | ||
var settings = new DebugLaunchSettings(launchOptions) | ||
{ | ||
LaunchDebugEngineGuid = Microsoft.VisualStudio.ProjectSystem.Debug.DebuggerEngines.ManagedOnlyEngine, | ||
LaunchOperation = DebugLaunchOperation.CreateProcess | ||
}; | ||
|
||
// try and get the target project | ||
var targetProjectUnconfigured = await TryGetTargetProjectAsync(profile).ConfigureAwait(false); | ||
if (targetProjectUnconfigured is object) | ||
{ | ||
settings.CurrentDirectory = Path.GetDirectoryName(targetProjectUnconfigured.FullPath); | ||
var compiler = _configuredProject.Capabilities.Contains(ProjectCapabilities.VB) ? "vbc.exe" : "csc.exe"; | ||
var compilerRoot = await _compilerRoot.GetValueAsync().ConfigureAwait(false); | ||
settings.Executable = Path.Combine(compilerRoot, compiler); | ||
|
||
// try and get the configured version of the target project | ||
var targetProject = await targetProjectUnconfigured.GetSuggestedConfiguredProjectAsync().ConfigureAwait(false); | ||
if (targetProject is object) | ||
{ | ||
// get its compilation args | ||
var args = await targetProject.GetCompilationArgumentsAsync().ConfigureAwait(false); | ||
|
||
// append the command line args to the debugger launch | ||
settings.Arguments = string.Join(" ", args); | ||
chsienki marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
} | ||
|
||
// https://github.com/dotnet/roslyn-sdk/issues/728 : better error handling | ||
return new IDebugLaunchSettings[] { settings }; | ||
} | ||
|
||
private static async Task<string> GetCompilerRootAsync(SVsServiceProvider? serviceProvider) | ||
{ | ||
await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); | ||
|
||
// https://github.com/dotnet/roslyn-sdk/issues/729 | ||
object rootDir = string.Empty; | ||
var shell = (IVsShell?)serviceProvider?.GetService(typeof(SVsShell)); | ||
shell?.GetProperty((int)__VSSPROPID2.VSSPROPID_InstallRootDir, out rootDir); | ||
return Path.Combine((string)rootDir, "MSBuild", "Current", "Bin", "Roslyn"); | ||
} | ||
|
||
private async Task<UnconfiguredProject?> TryGetTargetProjectAsync(ILaunchProfile? profile) | ||
{ | ||
UnconfiguredProject? targetProject = null; | ||
object? value = null; | ||
profile?.OtherSettings?.TryGetValue(Constants.TargetProjectPropertyName, out value); | ||
|
||
if (value is string targetProjectPath) | ||
{ | ||
// expand any variables in the path, and root it based on this project | ||
var replacedProjectPath = await _tokenReplacer.ReplaceTokensInStringAsync(targetProjectPath, true).ConfigureAwait(false); | ||
replacedProjectPath = _configuredProject.UnconfiguredProject.MakeRooted(replacedProjectPath); | ||
|
||
targetProject = _configuredProject.Services.ProjectService.LoadedUnconfiguredProjects.SingleOrDefault(p => p.FullPath == replacedProjectPath); | ||
} | ||
|
||
return targetProject; | ||
} | ||
} | ||
} |
19 changes: 19 additions & 0 deletions
19
src/VisualStudio.Roslyn.SDK/ComponentDebugger/DebuggerOptions.xaml
This file contains 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,19 @@ | ||
<UserControl x:Class="Roslyn.ComponentDebugger.DebuggerOptions" | ||
x:ClassModifier="internal" | ||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" | ||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" | ||
Width="Auto" | ||
HorizontalAlignment="Left" | ||
VerticalAlignment="Top"> | ||
<Grid Margin="0,2,0,0"> | ||
<Grid.ColumnDefinitions> | ||
<ColumnDefinition Width="158" /> | ||
<ColumnDefinition Width="350" /> | ||
<ColumnDefinition Width="Auto" /> | ||
</Grid.ColumnDefinitions> | ||
|
||
<!-- https://github.com/dotnet/roslyn-sdk/issues/730 : Localization --> | ||
<Label Margin="4,4,3,5">Target Project:</Label> | ||
<ComboBox Grid.Column="1" Margin="5,7,2,6" ItemsSource="{Binding ProjectNames}" SelectedIndex="{Binding SelectedProjectIndex, Mode=TwoWay}" /> | ||
</Grid> | ||
</UserControl> |
19 changes: 19 additions & 0 deletions
19
src/VisualStudio.Roslyn.SDK/ComponentDebugger/DebuggerOptions.xaml.cs
This file contains 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,19 @@ | ||
// 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 file in the project root for more information. | ||
|
||
using System.Windows.Controls; | ||
|
||
namespace Roslyn.ComponentDebugger | ||
{ | ||
/// <summary> | ||
/// Interaction logic for DebuggerOptions.xaml | ||
/// </summary> | ||
internal sealed partial class DebuggerOptions : UserControl | ||
{ | ||
public DebuggerOptions() | ||
{ | ||
InitializeComponent(); | ||
} | ||
} | ||
} |
74 changes: 74 additions & 0 deletions
74
src/VisualStudio.Roslyn.SDK/ComponentDebugger/DebuggerOptionsViewModel.cs
This file contains 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,74 @@ | ||
// 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 file in the project root for more information. | ||
|
||
using System; | ||
using System.Collections.Generic; | ||
using System.Collections.Immutable; | ||
using System.ComponentModel; | ||
using System.IO; | ||
using System.Linq; | ||
using System.Text; | ||
using System.Threading.Tasks; | ||
using Microsoft.VisualStudio.ProjectSystem; | ||
using Microsoft.VisualStudio.ProjectSystem.Debug; | ||
|
||
namespace Roslyn.ComponentDebugger | ||
{ | ||
internal class DebuggerOptionsViewModel : INotifyPropertyChanged | ||
{ | ||
private IWritableLaunchProfile? _launchProfile; | ||
|
||
private readonly ImmutableArray<ConfiguredProject> _targetProjects; | ||
|
||
private readonly IEnumerable<string> _targetProjectNames; | ||
|
||
public event PropertyChangedEventHandler? PropertyChanged; | ||
|
||
public DebuggerOptionsViewModel(ImmutableArray<ConfiguredProject> targetProjects) | ||
{ | ||
_targetProjects = targetProjects; | ||
_targetProjectNames = _targetProjects.Select(t => Path.GetFileNameWithoutExtension(t.UnconfiguredProject.FullPath)); | ||
} | ||
|
||
public IEnumerable<string> ProjectNames { get => _targetProjectNames; } | ||
|
||
public IWritableLaunchProfile? LaunchProfile | ||
{ | ||
get => _launchProfile; | ||
set | ||
{ | ||
_launchProfile = value; | ||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(SelectedProjectIndex))); | ||
} | ||
} | ||
|
||
public int SelectedProjectIndex | ||
{ | ||
get | ||
{ | ||
if (LaunchProfile?.OtherSettings.ContainsKey(Constants.TargetProjectPropertyName) == true) | ||
{ | ||
var target = LaunchProfile.OtherSettings[Constants.TargetProjectPropertyName].ToString(); | ||
for (var i = 0; i < _targetProjects.Length; i++) | ||
{ | ||
if (_targetProjects[i].UnconfiguredProject.FullPath.Equals(target, StringComparison.OrdinalIgnoreCase)) | ||
{ | ||
return i; | ||
} | ||
} | ||
} | ||
return -1; | ||
} | ||
set | ||
{ | ||
if (LaunchProfile is object) | ||
{ | ||
var newTargetProject = _targetProjects[value].UnconfiguredProject; | ||
LaunchProfile.OtherSettings[Constants.TargetProjectPropertyName] = newTargetProject.FullPath; | ||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(SelectedProjectIndex))); | ||
} | ||
} | ||
} | ||
} | ||
} |
Oops, something went wrong.
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.
This is mixing dataflow and live project data, which you can't do.
You need to listen to and pull on the ProjectInstance to get the property via dataflow, here's an example: https://github.com/dotnet/project-system/blob/b7fd4c439d1d39b656d89a8b88464fabffcc8e59/src/Microsoft.VisualStudio.ProjectSystem.Managed.VS/ProjectSystem/VS/Web/ProjectTypeGuidsDataSource.cs.
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.
@davkean Oh, interesting. There are classes in the devdiv repo that use this exact pattern. The base class seems to subscribe to events, so I figured it was designed to explicitly not need dataflow stuff.
I'll try and use the example to create a dataflow version.