Skip to content

Commit

Permalink
Component debugger (#738)
Browse files Browse the repository at this point in the history
* Component Debugger 
- See #726

* Add command line data source:
- add data source to obtain command line args
- use data source in extension method
- extract out shared logic into launchsettingsmanager
- clean up VM code

* Remove capability provider
- Use targets directly instead
  • Loading branch information
chsienki authored Mar 8, 2021
1 parent ceed1c5 commit 05749cb
Show file tree
Hide file tree
Showing 13 changed files with 513 additions and 0 deletions.
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)
: 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)]
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();

// 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;
}
}
}
19 changes: 19 additions & 0 deletions src/VisualStudio.Roslyn.SDK/ComponentDebugger/DebuggerOptions.xaml
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>
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

0 comments on commit 05749cb

Please sign in to comment.