Skip to content
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

Component debugger #738

Merged
merged 9 commits into from
Mar 8, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions NuGet.config
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
<add key="dotnet-tools" value="https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-tools/nuget/v3/index.json" />
<add key="dotnet-eng" value="https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-eng/nuget/v3/index.json" />
<add key="dotnet5" value="https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet5/nuget/v3/index.json" />
<add key="vs-impl" value="https://pkgs.dev.azure.com/azure-public/vside/_packaging/vs-impl/nuget/v3/index.json" />
</packageSources>
<disabledPackageSources />
</configuration>
7 changes: 7 additions & 0 deletions Roslyn-SDK.sln
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,8 @@ Project("{778DAE3C-4631-46EA-AA77-85C1314464D9}") = "Microsoft.CodeAnalysis.Visu
EndProject
Project("{778DAE3C-4631-46EA-AA77-85C1314464D9}") = "Microsoft.CodeAnalysis.VisualBasic.SourceGenerators.Testing.XUnit.UnitTests", "tests\Microsoft.CodeAnalysis.Testing\Microsoft.CodeAnalysis.VisualBasic.SourceGenerators.Testing.XUnit.UnitTests\Microsoft.CodeAnalysis.VisualBasic.SourceGenerators.Testing.XUnit.UnitTests.vbproj", "{92BD1781-5DB4-4F72-BCCB-0D64C0790A2B}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Roslyn.ComponentDebugger", "src\VisualStudio.Roslyn.SDK\ComponentDebugger\Roslyn.ComponentDebugger.csproj", "{421DE59C-8246-4679-9D69-79F16A7187BE}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -539,6 +541,10 @@ Global
{92BD1781-5DB4-4F72-BCCB-0D64C0790A2B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{92BD1781-5DB4-4F72-BCCB-0D64C0790A2B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{92BD1781-5DB4-4F72-BCCB-0D64C0790A2B}.Release|Any CPU.Build.0 = Release|Any CPU
{421DE59C-8246-4679-9D69-79F16A7187BE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{421DE59C-8246-4679-9D69-79F16A7187BE}.Debug|Any CPU.Build.0 = Debug|Any CPU
{421DE59C-8246-4679-9D69-79F16A7187BE}.Release|Any CPU.ActiveCfg = Release|Any CPU
{421DE59C-8246-4679-9D69-79F16A7187BE}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down Expand Up @@ -635,6 +641,7 @@ Global
{7D9C0EF5-7383-4E35-811B-3288B3C806F3} = {9905147E-CC1F-42A0-BD27-05586C583DF7}
{7C3FE60E-055B-4E0C-BB85-C7E94A640074} = {9905147E-CC1F-42A0-BD27-05586C583DF7}
{92BD1781-5DB4-4F72-BCCB-0D64C0790A2B} = {9905147E-CC1F-42A0-BD27-05586C583DF7}
{421DE59C-8246-4679-9D69-79F16A7187BE} = {F9B73995-76C6-4056-ADA9-18342F951361}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {56695AA9-EA80-47A7-8562-E51285906C54}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
// 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.Immutable;
using System.ComponentModel.Composition;
using System.Threading;
using System.Threading.Tasks;
using System.Threading.Tasks.Dataflow;
using Microsoft.VisualStudio.ProjectSystem;
using Microsoft.VisualStudio.ProjectSystem.VS;

namespace Roslyn.ComponentDebugger
{
[Export]
[AppliesTo("(" + ProjectCapabilities.CSharp + " | " + ProjectCapabilities.VB + ") & !" + ProjectCapabilities.SharedAssetsProject)]
public class CommandLineArgumentsDataSource : UnconfiguredProjectHostBridge<IProjectVersionedValue<IProjectSubscriptionUpdate>, IProjectVersionedValue<ImmutableArray<string>>, IProjectVersionedValue<ImmutableArray<string>>>
{
private readonly IActiveConfiguredProjectSubscriptionService _activeProjectSubscriptionService;

[ImportingConstructor]
[System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1062:Validate arguments of public methods", Justification = "MEF ensures not null")]
public CommandLineArgumentsDataSource(IProjectThreadingService projectThreadingService, IActiveConfiguredProjectSubscriptionService activeProjectSubscriptionService)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
public CommandLineArgumentsDataSource(IProjectThreadingService projectThreadingService, IActiveConfiguredProjectSubscriptionService activeProjectSubscriptionService)
public CommandLineArgumentsDataSource(IProjectThreadingServicea? projectThreadingService, IActiveConfiguredProjectSubscriptionService activeProjectSubscriptionService)

Given the projectThreadingService?. below this seems to be the correct annotation

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, its not. the ? is just to shut up the analyzer which doesn't seem to be nullable aware :/

I'll explicitly suppress it though, to make it clear.

: base(projectThreadingService.JoinableTaskContext)
{
_activeProjectSubscriptionService = activeProjectSubscriptionService;
}

public async Task<ImmutableArray<string>> GetArgsAsync()
{
using (JoinableCollection.Join())
{
await this.InitializeAsync().ConfigureAwait(true);
return this.AppliedValue?.Value ?? ImmutableArray<string>.Empty;
}
}

protected override bool BlockInitializeOnFirstAppliedValue => true;

protected override Task InitializeInnerCoreAsync(CancellationToken cancellationToken) => Task.CompletedTask;

protected override IDisposable LinkExternalInput(ITargetBlock<IProjectVersionedValue<IProjectSubscriptionUpdate>> targetBlock)
{
JoinUpstreamDataSources(_activeProjectSubscriptionService.ProjectBuildRuleSource);
return _activeProjectSubscriptionService.ProjectBuildRuleSource.SourceBlock.LinkTo(target: targetBlock,
linkOptions: new DataflowLinkOptions { PropagateCompletion = true },
initialDataAsNew: true,
suppressVersionOnlyUpdates: true,
ruleNames: Constants.CommandLineArgsRuleName);
}

protected override Task<IProjectVersionedValue<ImmutableArray<string>>> PreprocessAsync(IProjectVersionedValue<IProjectSubscriptionUpdate> input, IProjectVersionedValue<ImmutableArray<string>>? previousOutput)
{
if (input is null)
{
throw new ArgumentNullException(nameof(input));
}

var description = input.Value.ProjectChanges[Constants.CommandLineArgsRuleName];
return Task.FromResult<IProjectVersionedValue<ImmutableArray<string>>>(new ProjectVersionedValue<ImmutableArray<string>>(description.After.Items.Keys.ToImmutableArray(), input.DataSourceVersions));
}

protected override Task ApplyAsync(IProjectVersionedValue<ImmutableArray<string>> value)
{
AppliedValue = value;
return Task.CompletedTask;
}
}
}
17 changes: 17 additions & 0 deletions src/VisualStudio.Roslyn.SDK/ComponentDebugger/Constants.cs
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 RoslynComponentCapability = "RoslynComponent";

public const string CommandName = "DebugRoslynComponent";

public const string TargetProjectKeyName = "targetProject";

public const string CommandLineArgsRuleName = "CompilerCommandLineArgs";
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
// 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;
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)]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we want to update the projects in the templates to have this capability?

public class DebugProfileProvider : IDebugProfileLaunchTargetsProvider
{
private readonly ConfiguredProject _configuredProject;
private readonly LaunchSettingsManager _launchSettingsManager;
private readonly IProjectThreadingService _threadingService;
private readonly AsyncLazy<string?> _compilerRoot;

[ImportingConstructor]
[Obsolete("This exported object must be obtained through the MEF export provider.", error: true)]
public DebugProfileProvider(ConfiguredProject configuredProject, LaunchSettingsManager launchSettingsManager, SVsServiceProvider? serviceProvider, IProjectThreadingService threadingService)
{
_configuredProject = configuredProject;
_launchSettingsManager = launchSettingsManager;
_threadingService = threadingService;

_compilerRoot = new AsyncLazy<string?>(() => GetCompilerRootAsync(serviceProvider), _threadingService.JoinableTaskFactory);
}

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.Ordinal);

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
};

var compilerRoot = await _compilerRoot.GetValueAsync().ConfigureAwait(true);
if (compilerRoot is object)
{
// try and get the target project
var targetProjectUnconfigured = await _launchSettingsManager.TryGetProjectForLaunchAsync(profile).ConfigureAwait(true);
if (targetProjectUnconfigured is object)
{
settings.CurrentDirectory = Path.GetDirectoryName(targetProjectUnconfigured.FullPath);
var compiler = _configuredProject.Capabilities.Contains(ProjectCapabilities.VB) ? "vbc.exe" : "csc.exe";
settings.Executable = Path.Combine(compilerRoot, compiler);

// get its compilation args
var args = await targetProjectUnconfigured.GetCompilationArgumentsAsync().ConfigureAwait(true);

// append the command line args to the debugger launch
settings.Arguments = string.Join(" ", args);
}
}
// https://github.com/dotnet/roslyn-sdk/issues/728 : better error handling
return new IDebugLaunchSettings[] { settings };
}

private async Task<string?> GetCompilerRootAsync(SVsServiceProvider? serviceProvider)
{
await _threadingService.SwitchToUIThread();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we not need a ConfigureAwait here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The explicit point of this is to change the captured context: .ConfigureAwait(true) would be incorrect, .ConfigureAwait(false) would be redundant, so the analyzer doesn't complain about it.


// https://github.com/dotnet/roslyn-sdk/issues/729 : don't hardcode net fx compiler
var shell = (IVsShell?)serviceProvider?.GetService(typeof(SVsShell));
if (shell is object
&& shell.GetProperty((int)__VSSPROPID2.VSSPROPID_InstallRootDir, out var rootDirObj) == VSConstants.S_OK
&& rootDirObj is string rootDir)
{
return Path.Combine(rootDir, "MSBuild", "Current", "Bin", "Roslyn");
}

return null;
}
}
}
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}" />
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Chris is a WPF / XAM expert for the compiler team. That is my take away here.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Haha, I used to be paid to write windows phone apps... sooo.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh wow, I was joking ... but you are an expert. Now I know who to assign all our WPF bugs too ... ;)

</Grid>
</UserControl>
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
// 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.Linq;
using System.Runtime.CompilerServices;

namespace Roslyn.ComponentDebugger
{
internal sealed class DebuggerOptionsViewModel : INotifyPropertyChanged
{
private readonly Action<int> _indexChanged;

private IEnumerable<string> _projectNames = ImmutableArray<string>.Empty;

private int _selectedProjectIndex = -1;

public event PropertyChangedEventHandler? PropertyChanged;

public DebuggerOptionsViewModel(Action<int> indexChanged)
{
_indexChanged = indexChanged;
}

public IEnumerable<string> ProjectNames
{
get => _projectNames;
set
{
if (!_projectNames.SequenceEqual(value))
{
_projectNames = value;
NotifyPropertyChanged();
}
}
}

public int SelectedProjectIndex
{
get => _selectedProjectIndex;
set
{
if (_selectedProjectIndex != value)
{
_selectedProjectIndex = value;
NotifyPropertyChanged();
_indexChanged?.Invoke(value);
}
}
}

private void NotifyPropertyChanged([CallerMemberName]string propertyName = "")
{
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
// 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.ComponentModel.Composition;
using System.Threading.Tasks;
using Microsoft.VisualStudio.ProjectSystem;
using Microsoft.VisualStudio.ProjectSystem.Debug;

namespace Roslyn.ComponentDebugger
{
[Export]
public class LaunchSettingsManager
{
private readonly UnconfiguredProject _owningProject;
private readonly IDebugTokenReplacer _tokenReplacer;

[ImportingConstructor]
public LaunchSettingsManager(UnconfiguredProject owningProject, IDebugTokenReplacer tokenReplacer)
{
_owningProject = owningProject;
_tokenReplacer = tokenReplacer;
}

public async Task<UnconfiguredProject?> TryGetProjectForLaunchAsync(ILaunchProfile? profile)
{
UnconfiguredProject? targetProject = null;
object? value = null;
profile?.OtherSettings?.TryGetValue(Constants.TargetProjectKeyName, 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(true);
replacedProjectPath = _owningProject.MakeRooted(replacedProjectPath);

targetProject = ((IProjectService2)_owningProject.Services.ProjectService).GetLoadedProject(replacedProjectPath);
}
return targetProject;
}

public void WriteProjectForLaunch(IWritableLaunchProfile profile, UnconfiguredProject targetProject)
{
if (profile is null)
{
throw new System.ArgumentNullException(nameof(profile));
}

if (targetProject is null)
{
throw new System.ArgumentNullException(nameof(targetProject));
}

var rootedPath = _owningProject.MakeRelative(targetProject.FullPath);
profile.OtherSettings[Constants.TargetProjectKeyName] = rootedPath;
}

}
}
Loading