Build project before running

This commit is contained in:
Matt Parker
2026-01-18 16:49:03 +10:00
parent 77543915a8
commit fc9e6ef3ec
8 changed files with 21 additions and 15 deletions

View File

@@ -120,7 +120,7 @@ public partial class RoslynAnalysis(ILogger<RoslynAnalysis> logger, BuildService
using (var ___ = SharpIdeOtel.Source.StartActivity("RestoreSolution"))
{
// MsBuildProjectLoader doesn't do a restore which is absolutely required for resolving PackageReferences, if they have changed. I am guessing it just reads from project.assets.json
await _buildService.MsBuildAsync(_sharpIdeSolutionModel.FilePath, BuildType.Restore, cancellationToken);
await _buildService.MsBuildAsync(_sharpIdeSolutionModel.FilePath, BuildType.Restore, BuildStartedFlags.UserFacing, cancellationToken);
}
using (var ___ = SharpIdeOtel.Source.StartActivity("OpenSolution"))
{
@@ -187,7 +187,7 @@ public partial class RoslynAnalysis(ILogger<RoslynAnalysis> logger, BuildService
Guard.Against.Null(_msBuildProjectLoader, nameof(_msBuildProjectLoader));
// It is important to note that a Workspace has no concept of MSBuild, nuget packages etc. It is just told about project references and "metadata" references, which are dlls. This is the what MSBuild does - it reads the csproj, and most importantly resolves nuget package references to dlls
await _buildService.MsBuildAsync(_sharpIdeSolutionModel!.FilePath, BuildType.Restore, cancellationToken);
await _buildService.MsBuildAsync(_sharpIdeSolutionModel!.FilePath, BuildType.Restore, BuildStartedFlags.UserFacing, cancellationToken);
var __ = SharpIdeOtel.Source.StartActivity($"{nameof(RoslynAnalysis)}.MSBuildProjectLoader.LoadSolutionInfoAsync");
// This call is the expensive part - MSBuild is slow. There doesn't seem to be any incrementalism for solutions.
// The best we could do to speed it up is do .LoadProjectInfoAsync for the single project, and somehow munge that into the existing solution
@@ -214,7 +214,7 @@ public partial class RoslynAnalysis(ILogger<RoslynAnalysis> logger, BuildService
Guard.Against.Null(_workspace, nameof(_workspace));
Guard.Against.Null(_msBuildProjectLoader, nameof(_msBuildProjectLoader));
await _buildService.MsBuildAsync(_sharpIdeSolutionModel!.FilePath, BuildType.Restore, cancellationToken);
await _buildService.MsBuildAsync(_sharpIdeSolutionModel!.FilePath, BuildType.Restore, BuildStartedFlags.Internal, cancellationToken);
var __ = SharpIdeOtel.Source.StartActivity($"{nameof(RoslynAnalysis)}.{nameof(CustomMsBuildProjectLoader)}.{nameof(CustomMsBuildProjectLoader.LoadProjectInfosAsync)}");
var thisProject = GetProjectForSharpIdeProjectModel(projectModel);

View File

@@ -16,15 +16,16 @@ public enum BuildType
Clean,
Restore
}
public enum BuildStartedFlags { UserFacing = 0, Internal }
public class BuildService(ILogger<BuildService> logger)
{
private readonly ILogger<BuildService> _logger = logger;
public EventWrapper<Task> BuildStarted { get; } = new(() => Task.CompletedTask);
public EventWrapper<BuildStartedFlags, Task> BuildStarted { get; } = new(_ => Task.CompletedTask);
public EventWrapper<Task> BuildFinished { get; } = new(() => Task.CompletedTask);
public ChannelTextWriter BuildTextWriter { get; } = new ChannelTextWriter();
private CancellationTokenSource? _cancellationTokenSource;
public async Task MsBuildAsync(string solutionOrProjectFilePath, BuildType buildType = BuildType.Build, CancellationToken cancellationToken = default)
public async Task MsBuildAsync(string solutionOrProjectFilePath, BuildType buildType = BuildType.Build, BuildStartedFlags buildStartedFlags = BuildStartedFlags.UserFacing, CancellationToken cancellationToken = default)
{
if (_cancellationTokenSource is not null) throw new InvalidOperationException("A build is already in progress.");
_cancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
@@ -55,7 +56,7 @@ public class BuildService(ILogger<BuildService> logger)
hostServices: null,
flags: BuildRequestDataFlags.None);
BuildStarted.InvokeParallelFireAndForget();
BuildStarted.InvokeParallelFireAndForget(buildStartedFlags);
var timer = Stopwatch.StartNew();
var buildResult = await BuildManager.DefaultBuildManager.BuildAsync(buildParameters, buildRequest, _cancellationTokenSource.Token).ConfigureAwait(false);
timer.Stop();

View File

@@ -5,6 +5,7 @@ using AsyncReadProcess;
using Microsoft.Extensions.Logging;
using Microsoft.VisualStudio.Shared.VSCodeDebugProtocol.Messages;
using SharpIDE.Application.Features.Analysis;
using SharpIDE.Application.Features.Build;
using SharpIDE.Application.Features.Debugging;
using SharpIDE.Application.Features.Evaluation;
using SharpIDE.Application.Features.Events;
@@ -14,13 +15,14 @@ using Breakpoint = SharpIDE.Application.Features.Debugging.Breakpoint;
namespace SharpIDE.Application.Features.Run;
public partial class RunService(ILogger<RunService> logger, RoslynAnalysis roslynAnalysis)
public partial class RunService(ILogger<RunService> logger, RoslynAnalysis roslynAnalysis, BuildService buildService)
{
private readonly ConcurrentDictionary<SharpIdeProjectModel, SemaphoreSlim> _projectLocks = [];
private Debugger? _debugger; // TODO: Support multiple debuggers for multiple running projects
private readonly ILogger<RunService> _logger = logger;
private readonly RoslynAnalysis _roslynAnalysis = roslynAnalysis;
private readonly BuildService _buildService = buildService;
public async Task RunProject(SharpIdeProjectModel project, bool isDebug = false, DebuggerExecutableInfo? debuggerExecutableInfo = null)
{
@@ -32,8 +34,10 @@ public partial class RunService(ILogger<RunService> logger, RoslynAnalysis rosly
var waitResult = await semaphoreSlim.WaitAsync(0).ConfigureAwait(false);
if (waitResult is false) throw new InvalidOperationException($"Project {project.Name} is already running.");
if (project.RunningCancellationTokenSource is not null) throw new InvalidOperationException($"Project {project.Name} is already running with a cancellation token source.");
project.RunningCancellationTokenSource = new CancellationTokenSource();
await _buildService.MsBuildAsync(project.FilePath);
var launchProfiles = await LaunchSettingsParser.GetLaunchSettingsProfiles(project);
var launchProfile = launchProfiles.FirstOrDefault();
try