From e42ac24f3ac6de60d8d53877be16b224f11e8319 Mon Sep 17 00:00:00 2001 From: LPeter1997 Date: Wed, 28 Aug 2024 20:23:04 +0200 Subject: [PATCH] Design time build fixes (#449) * Update Draco.ProjectSystem.csproj * Restore added * Minor fixes * Update Draco.LanguageServer.csproj --- .../Draco.LanguageServer.csproj | 7 +- .../DracoLanguageServer.cs | 9 +- src/Draco.ProjectSystem/BuildResult.cs | 49 ++++++++++ src/Draco.ProjectSystem/DesignTimeBuild.cs | 16 +-- .../Draco.ProjectSystem.csproj | 10 +- src/Draco.ProjectSystem/Project.cs | 98 ++++++++++++------- src/Draco.ProjectSystem/Unit.cs | 12 +++ 7 files changed, 143 insertions(+), 58 deletions(-) create mode 100644 src/Draco.ProjectSystem/BuildResult.cs create mode 100644 src/Draco.ProjectSystem/Unit.cs diff --git a/src/Draco.LanguageServer/Draco.LanguageServer.csproj b/src/Draco.LanguageServer/Draco.LanguageServer.csproj index 66564d421..536908955 100644 --- a/src/Draco.LanguageServer/Draco.LanguageServer.csproj +++ b/src/Draco.LanguageServer/Draco.LanguageServer.csproj @@ -16,7 +16,12 @@ - + + + + + + diff --git a/src/Draco.LanguageServer/DracoLanguageServer.cs b/src/Draco.LanguageServer/DracoLanguageServer.cs index e1ba0f0e0..5fc45dd38 100644 --- a/src/Draco.LanguageServer/DracoLanguageServer.cs +++ b/src/Draco.LanguageServer/DracoLanguageServer.cs @@ -73,14 +73,17 @@ private async Task CreateCompilation() } var project = projects[0]; - var designTimeBuild = project.BuildDesignTime(); + var buildResult = project.BuildDesignTime(); - if (!designTimeBuild.Succeeded) + if (!buildResult.Success) { - await this.client.LogMessageAsync(MessageType.Error, designTimeBuild.BuildLog); + await this.client.LogMessageAsync(MessageType.Error, buildResult.Log); await this.client.ShowMessageAsync(MessageType.Error, "Design-time build failed! See logs for details."); + return; } + var designTimeBuild = buildResult.Value; + var syntaxTrees = Directory .GetFiles(rootPath, "*.draco", SearchOption.AllDirectories) .Select(x => SyntaxTree.Parse(SourceText.FromFile(x))) diff --git a/src/Draco.ProjectSystem/BuildResult.cs b/src/Draco.ProjectSystem/BuildResult.cs new file mode 100644 index 000000000..b718a0848 --- /dev/null +++ b/src/Draco.ProjectSystem/BuildResult.cs @@ -0,0 +1,49 @@ +using System.Diagnostics.CodeAnalysis; + +namespace Draco.ProjectSystem; + +/// +/// Factory for creating build results. +/// +internal static class BuildResult +{ + /// + /// Creates a successful build result. + /// + /// The result type of a successful build. + /// The result of the build. + /// The log of the build. + /// A successful build result. + public static BuildResult Success(T value, string? log = null) => new(true, value, log); + + /// + /// Creates a failed build result. + /// + /// The result type of a successful build. + /// The log of the build. + /// A failed build result. + public static BuildResult Failure(string? log = null) => new(false, default, log); +} + +/// +/// The result of a build. +/// +/// The result type of a successful build. +public readonly struct BuildResult(bool success, T? value, string? log = null) +{ + /// + /// True, if the build succeeded. + /// + [MemberNotNullWhen(true, nameof(Value))] + public bool Success { get; } = success; + + /// + /// The result of the build. + /// + public T? Value { get; } = value; + + /// + /// The log of the build. + /// + public string Log { get; } = log ?? string.Empty; +} diff --git a/src/Draco.ProjectSystem/DesignTimeBuild.cs b/src/Draco.ProjectSystem/DesignTimeBuild.cs index 81ba17bef..8f71d8f81 100644 --- a/src/Draco.ProjectSystem/DesignTimeBuild.cs +++ b/src/Draco.ProjectSystem/DesignTimeBuild.cs @@ -15,16 +15,6 @@ public readonly struct DesignTimeBuild /// string public Project Project { get; } - /// - /// True, if the build succeeded. - /// - public bool Succeeded { get; } - - /// - /// The build log. - /// - public string BuildLog { get; } - /// /// The reference paths of the project. /// @@ -40,13 +30,9 @@ public readonly struct DesignTimeBuild internal DesignTimeBuild( Project project, - MSBuildProjectInstance projectInstance, - bool success, - string buildLog) + MSBuildProjectInstance projectInstance) { this.Project = project; this.ProjectInstance = projectInstance; - this.Succeeded = success; - this.BuildLog = buildLog; } } diff --git a/src/Draco.ProjectSystem/Draco.ProjectSystem.csproj b/src/Draco.ProjectSystem/Draco.ProjectSystem.csproj index f4bf6641a..ef36a0544 100644 --- a/src/Draco.ProjectSystem/Draco.ProjectSystem.csproj +++ b/src/Draco.ProjectSystem/Draco.ProjectSystem.csproj @@ -2,10 +2,10 @@ - - - - - + + + + + diff --git a/src/Draco.ProjectSystem/Project.cs b/src/Draco.ProjectSystem/Project.cs index 5405ef9ba..2231511a2 100644 --- a/src/Draco.ProjectSystem/Project.cs +++ b/src/Draco.ProjectSystem/Project.cs @@ -3,6 +3,7 @@ using System.IO; using System.Linq; using Microsoft.Build.Evaluation; +using Microsoft.Build.Framework; using MSBuildProject = Microsoft.Build.Evaluation.Project; using MSBuildProjectInstance = Microsoft.Build.Execution.ProjectInstance; @@ -43,12 +44,7 @@ public string? TargetFramework /// /// Project for reading out the target framework(s). /// - internal MSBuildProject TfmProject => this.tfmProject ??= new( - projectFile: this.ProjectFile.FullName, - projectCollection: this.Workspace.ProjectCollection, - globalProperties: GetGlobalProperties(), - toolsVersion: null, - loadSettings: ProjectLoadSettings.IgnoreMissingImports | ProjectLoadSettings.IgnoreInvalidImports); + internal MSBuildProject TfmProject => this.tfmProject ??= this.CreateTfmProject(); private MSBuildProject? tfmProject; /// @@ -61,28 +57,7 @@ public string? TargetFramework /// /// Project for design-time builds. /// - internal MSBuildProject BuildProject - { - get - { - if (this.buildProject is null) - { - var globalProps = GetGlobalProperties(); - var targetFramework = this.TargetFramework; - if (targetFramework is not null) - { - globalProps.Add("TargetFramework", targetFramework); - } - this.buildProject = new( - projectFile: this.ProjectFile.FullName, - projectCollection: this.Workspace.ProjectCollection, - globalProperties: globalProps, - toolsVersion: null, - loadSettings: ProjectLoadSettings.IgnoreMissingImports | ProjectLoadSettings.IgnoreInvalidImports); - } - return this.buildProject; - } - } + internal MSBuildProject BuildProject => this.buildProject ??= this.CreateBuildProject(); private MSBuildProject? buildProject; internal Project(FileInfo projectFile, Workspace workspace) @@ -91,20 +66,72 @@ internal Project(FileInfo projectFile, Workspace workspace) this.Workspace = workspace; } + /// + /// Performs a restore of the project. + /// + /// The result of the restore. + public BuildResult Restore() + { + var log = new StringWriter(); + var logger = new StringWriterLogger(log); + + var buildTargets = GetRestoreBuildTargets(); + var projectInstance = this.BuildProject.CreateProjectInstance(); + var success = projectInstance.Build(buildTargets, [logger]); + + return success + ? BuildResult.Success(Unit.Default, log.ToString()) + : BuildResult.Failure(log.ToString()); + } + /// /// Performs a design-time build of the project. /// /// The result of the build. - public DesignTimeBuild BuildDesignTime() + public BuildResult BuildDesignTime() { - var stringLog = new StringWriter(); - var stringLogger = new StringWriterLogger(stringLog); + // First restore the project + var restoreResult = this.Restore(); + if (!restoreResult.Success) return BuildResult.Failure(restoreResult.Log); + + // NOTE: I have no idea why, but this is needed + // I suppose it's because a restore introduces files since the last run, something that the API doesn't handle? + // Mark the project as dirty + this.BuildProject.MarkDirty(); + + var log = new StringWriter(); + var logger = new StringWriterLogger(log); - var projectInstance = this.BuildProject.CreateProjectInstance(); var buildTargets = GetDesignTimeBuildTargets(); - var success = projectInstance.Build(buildTargets, [stringLogger]); + var projectInstance = this.BuildProject.CreateProjectInstance(); + var success = projectInstance.Build(buildTargets, [logger]); - return new(this, projectInstance, success, stringLog.ToString()); + return success + ? BuildResult.Success(new DesignTimeBuild(this, projectInstance), log.ToString()) + : BuildResult.Failure(log.ToString()); + } + + private MSBuildProject CreateTfmProject() => new( + projectFile: this.ProjectFile.FullName, + projectCollection: this.Workspace.ProjectCollection, + globalProperties: GetGlobalProperties(), + toolsVersion: null, + loadSettings: ProjectLoadSettings.IgnoreMissingImports | ProjectLoadSettings.IgnoreInvalidImports); + + private MSBuildProject CreateBuildProject() + { + var globalProps = GetGlobalProperties(); + var targetFramework = this.TargetFramework; + if (targetFramework is not null) + { + globalProps.Add("TargetFramework", targetFramework); + } + return new MSBuildProject( + projectFile: this.ProjectFile.FullName, + projectCollection: this.Workspace.ProjectCollection, + globalProperties: globalProps, + toolsVersion: null, + loadSettings: ProjectLoadSettings.IgnoreMissingImports | ProjectLoadSettings.IgnoreInvalidImports); } private static IDictionary GetGlobalProperties() => new Dictionary @@ -122,6 +149,9 @@ public DesignTimeBuild BuildDesignTime() ["DotnetProjInfo"] = "true", }; + private static string[] GetRestoreBuildTargets() => [ + "Restore"]; + private static string[] GetDesignTimeBuildTargets() => [ "ResolveAssemblyReferencesDesignTime", "ResolveProjectReferencesDesignTime", diff --git a/src/Draco.ProjectSystem/Unit.cs b/src/Draco.ProjectSystem/Unit.cs new file mode 100644 index 000000000..feee0efbe --- /dev/null +++ b/src/Draco.ProjectSystem/Unit.cs @@ -0,0 +1,12 @@ +namespace Draco.ProjectSystem; + +/// +/// A unit type representing the absence of a value. +/// +public readonly record struct Unit +{ + /// + /// A default instance of the unit type. + /// + public static readonly Unit Default = default; +}