From 66e4a6ae57bcee715fb57f638e5d0140b72da055 Mon Sep 17 00:00:00 2001 From: Chet Husk Date: Tue, 29 Mar 2022 19:53:01 -0500 Subject: [PATCH] Implement MSBuild functionality as a Task Provider (#1679) --- release/package.json | 114 +++++++----- src/Components/Debugger.fs | 98 +++++----- src/Components/MSBuild.fs | 357 ++++++++++++++++++++++++++++++------- src/Core/Utils.fs | 2 +- 4 files changed, 418 insertions(+), 153 deletions(-) diff --git a/release/package.json b/release/package.json index 23308ce9..25e6d974 100644 --- a/release/package.json +++ b/release/package.json @@ -237,16 +237,16 @@ }, { "command": "fsharp.explorer.showProjectLoadFailedInfo", - "title": "Show info about failed project loading", - "icon": { + "title": "Show info about failed project loading", + "icon": { "light": "./images/icon-status-light.svg", "dark": "./images/icon-status-dark.svg" } }, { "command": "fsharp.explorer.showProjectStatus", - "title": "Show project status", - "icon": { + "title": "Show project status", + "icon": { "light": "./images/icon-status-light.svg", "dark": "./images/icon-status-dark.svg" } @@ -803,8 +803,8 @@ }, { "command": "fsharp.explorer.showProjectLoadFailedInfo", - "when": "viewItem == ionide.projectExplorer.projectLoadFailed", - "group": "inline@98" + "when": "viewItem == ionide.projectExplorer.projectLoadFailed", + "group": "inline@98" }, { "command": "fsharp.explorer.openProjectFile", @@ -839,8 +839,8 @@ }, { "command": "fsharp.explorer.showProjectStatus", - "when": "viewItem == ionide.projectExplorer.projectNotRestored", - "group": "inline@98" + "when": "viewItem == ionide.projectExplorer.projectNotRestored", + "group": "inline@98" }, { "command": "fsharp.explorer.addProjecRef", @@ -1129,10 +1129,18 @@ { "language": "fsharp", "scopes": { - "mutable": ["variable.fsharp.mutable"], - "disposable": ["variable.fsharp.mutable"], - "operator": ["keyword.symbol.fsharp"], - "cexpr": ["keyword.control.fsharp"] + "mutable": [ + "variable.fsharp.mutable" + ], + "disposable": [ + "variable.fsharp.mutable" + ], + "operator": [ + "keyword.symbol.fsharp" + ], + "cexpr": [ + "keyword.control.fsharp" + ] } } ], @@ -1152,7 +1160,7 @@ "scope": "window" }, "FSharp.fsac.dotnetArgs": { - "type" : "array", + "type": "array", "default": [], "description": "additional CLI arguments to be provided to the dotnet runner for FSAC", "items": { @@ -1230,17 +1238,17 @@ "default": "failwith \"Not Implemented\"", "description": "The expression to fill in the right-hand side of record fields when generating missing fields for a record construction expression" }, - "FSharp.interfaceStubGeneration": { + "FSharp.interfaceStubGeneration": { "type": "boolean", "default": true, "description": "Enables a codefix that generates missing interface members when inside of an interface implementation expression" - }, - "FSharp.interfaceStubGenerationObjectIdentifier": { + }, + "FSharp.interfaceStubGenerationObjectIdentifier": { "type": "string", "default": "this", "description": "The name of the 'self' identifier in an interface member. For example, `this` in the expression `this.Member(x: int) = ()`" - }, - "FSharp.interfaceStubGenerationMethodBody": { + }, + "FSharp.interfaceStubGenerationMethodBody": { "type": "string", "default": "failwith \"Not Implemented\"", "description": "The expression to fill in the right-hand side of interface members when generating missing members for an interface implementation expression" @@ -1249,13 +1257,13 @@ "type": "boolean", "default": true, "description": "Enables a codefix that generates missing members for an abstract class when in an type inheriting from that abstract class." - }, - "FSharp.abstractClassStubGenerationObjectIdentifier": { + }, + "FSharp.abstractClassStubGenerationObjectIdentifier": { "type": "string", "default": "this", "description": "The name of the 'self' identifier in an inherited member. For example, `this` in the expression `this.Member(x: int) = ()`" - }, - "FSharp.abstractClassStubGenerationMethodBody": { + }, + "FSharp.abstractClassStubGenerationMethodBody": { "type": "string", "default": "failwith \"Not Implemented\"", "description": "The expression to fill in the right-hand side of inherited members when generating missing members for an abstract base class" @@ -1395,14 +1403,14 @@ ], "default": "sameAsFileExplorer", "scope": "window" - }, - "FSharp.smartIndent": { - "type": "boolean", - "default": false, - "description": "Enables smart indent feature" + }, + "FSharp.smartIndent": { + "type": "boolean", + "default": false, + "description": "Enables smart indent feature" }, "FSharp.infoPanelUpdate": { - "type": "string", + "type": "string", "description": "Controls when the info panel is updated", "enum": [ "onCursorMove", @@ -1410,22 +1418,22 @@ "both", "none" ], - "default": "onCursorMove" + "default": "onCursorMove" }, "FSharp.infoPanelReplaceHover": { - "type": "boolean", + "type": "boolean", "description": "Controls whether the info panel replaces tooltips", - "default": false + "default": false }, "FSharp.infoPanelStartLocked": { - "type": "boolean", + "type": "boolean", "description": "Controls whether the info panel should be locked at startup", - "default": false + "default": false }, "FSharp.infoPanelShowOnStartup": { - "type": "boolean", + "type": "boolean", "description": "Controls whether the info panel should be displayed at startup", - "default": false + "default": false }, "FSharp.verboseLogging": { "type": "boolean", @@ -1497,6 +1505,10 @@ { "fileMatch": "wsconfig.json", "url": "./schemas/wsconfig.json" + }, + { + "fileMatch": "**/launchSettings.json", + "url": "https://raw.githubusercontent.com/SchemaStore/schemastore/master/src/schemas/json/launchsettings.json" } ], "breakpoints": [ @@ -1560,22 +1572,32 @@ } ], "terminal": { - "profiles": [{ - "icon": "terminal", - "id": "ionide-fsharp.fsi", - "title": "F# Interactive" - }] - } + "profiles": [ + { + "icon": "terminal", + "id": "ionide-fsharp.fsi", + "title": "F# Interactive" + } + ] + }, + "taskDefinitions": [ + { + "type": "msbuild", + "required": [], + "properties": {} + } + ] }, "activationEvents": [ + "onCommand:fsharp.NewProject", + "onCommand:fsi.Start", + "onCommand:workbench.action.tasks.runTask", "onDebugDynamicConfigurations:coreclr", + "onLanguage:fsharp", "workspaceContains:**/*.fs", - "workspaceContains:**/*.fsx", "workspaceContains:**/*.fsproj", - "workspaceContains:**/*.sln", - "onLanguage:fsharp", - "onCommand:fsi.Start", - "onCommand:fsharp.NewProject" + "workspaceContains:**/*.fsx", + "workspaceContains:**/*.sln" ], "extensionDependencies": [ "ms-dotnettools.csharp" diff --git a/src/Components/Debugger.fs b/src/Components/Debugger.fs index ff87023a..cbcdc1b2 100644 --- a/src/Components/Debugger.fs +++ b/src/Components/Debugger.fs @@ -55,9 +55,10 @@ module LaunchJsonVersion2 = } module Debugger = + let outputChannel = window.createOutputChannel "Ionide: Debugger" let private logger = - ConsoleAndOutputChannelLogger(Some "Debug", Level.DEBUG, None, Some Level.DEBUG) + ConsoleAndOutputChannelLogger(Some "Debugger", Level.DEBUG, Some outputChannel, Some Level.DEBUG) let buildAndRun (project: Project) = promise { @@ -155,18 +156,10 @@ module Debugger = let chooseDefaultProject () = promise { - let projects = - Project.getInWorkspace () - |> List.choose (fun n -> - match n with - | Project.ProjectLoadingState.Loaded x -> Some x - | _ -> None) - - if projects.Length = 0 then - return None - elif projects.Length = 1 then - return Some projects.Head - else + match Project.getLoaded () with + | [] -> return None + | project::[] -> return Some project + | projects -> let picks = projects |> List.map (fun p -> @@ -252,7 +245,8 @@ module Debugger = ( name: string, ls: LaunchSettingsConfiguration, - project: Project + project: Project, + buildTaskOpt: Task option ) : DebugConfiguration option = if ls.commandName <> Some "Project" || ls.commandName = None then @@ -274,10 +268,15 @@ module Debugger = c.request <- "launch" c?program <- projectExecutable c?args <- cliArgs + match buildTaskOpt with + | Some bt -> c?preLaunchTask <- $"Build: {bt.name}" + | None -> () c?cwd <- ls.workingDirectory |> Option.defaultValue "${workspaceFolder}" + + match ls.launchBrowser with | Some true -> c?serverReadyAction <- {| action = "openExternally" @@ -287,14 +286,19 @@ module Debugger = if JS.isDefined ls.environmentVariables then let vars = ls.environmentVariables.Keys - |> Array.map (fun k -> k, box (Environment.expand (ls.environmentVariables[k]))) + |> Array.choose (fun k -> + let value = ls.environmentVariables[k] + if JS.isDefined value then + let replaced = Environment.expand value + Some (k, box replaced) + else None + ) c?env <- createObj vars if not (JS.isDefined ls.environmentVariables["ASPNETCORE_URLS"]) && Option.isSome ls.applicationUrl then c?env?ASPNETCORE_URLS <- ls.applicationUrl.Value - c?console <- "internalConsole" c?stopAtEntry <- false let presentation = {| hidden = false; group = "ionide" |} @@ -303,21 +307,21 @@ module Debugger = Some c - let configsForProject (project, launchSettings: JsMap) = + let configsForProject (project, launchSettings: JsMap, buildTaskOpt) = seq { for name in launchSettings.Keys do logger.Info $"Making config for {name}" let settings: LaunchSettingsConfiguration = launchSettings[name] if JS.isDefined settings then - match makeDebugConfigFor (name, settings, project) with + match makeDebugConfigFor (name, settings, project, buildTaskOpt) with | Some cfg -> yield cfg | None -> () else () } - let defaultConfigForProject (p: Project) : DebugConfiguration option = + let defaultConfigForProject (p: Project, buildTaskOpt: Task option) : DebugConfiguration option = if p.OutputType <> "exe" then None else @@ -331,35 +335,49 @@ module Debugger = c?console <- "internalConsole" c?stopAtEntry <- false + match buildTaskOpt with + | Some bt -> c?preLaunchTask <- $"Build: {bt.name}" + | None -> () + let presentation = {| hidden = false; group = "ionide" |} c?presentation <- presentation Some c let launchSettingProvider = + let msbuildTasksFilter: TaskFilter = + let f = createEmpty + f.``type`` <- Some "msbuild" + f + { new DebugConfigurationProvider with override x.provideDebugConfigurations(folder: option, token: option) = - logger.Info $"Evaluating launch settings configurations for workspace '%A{folder}'" - - Project.getInWorkspace () - |> Seq.choose (function - | Project.ProjectLoadingState.Loaded x -> Some x - | x -> - logger.Info $"Discarding project '{x}' because it is not loaded" - None) - |> Seq.collect (fun (p: Project) -> - seq { - // emit configurations for any launchsettings for this project - match readSettingsForProject p with - | Some launchSettings -> yield! configsForProject (p, launchSettings) - | None -> () - // emit a default configuration for this project if it is an executable - match defaultConfigForProject p with - | Some p -> yield p - | None -> () - }) - |> ResizeArray - |> U2.Case1 + let generate () = promise { + logger.Info $"Evaluating launch settings configurations for workspace '%A{folder}'" + let projects = Project.getLoaded() + let! msbuildTasks = tasks.fetchTasks(msbuildTasksFilter) + let tasks = + projects + |> Seq.collect (fun (p: Project) -> + seq { + let projectFile = path.basename p.Project + let buildTaskForProject = msbuildTasks |> Seq.tryFind (fun t -> t.group = Some vscode.TaskGroup.Build && t.name = projectFile) + // emit configurations for any launchsettings for this project + match readSettingsForProject p with + | Some launchSettings -> yield! configsForProject (p, launchSettings, buildTaskForProject) + | None -> () + // emit a default configuration for this project if it is an executable + match defaultConfigForProject (p, buildTaskForProject) with + | Some p -> yield p + | None -> () + }) + return ResizeArray tasks + } + + generate() + |> Promise.map Some + |> Promise.toThenable + |> U2.Case2 |> ProviderResult.Some override x.resolveDebugConfiguration diff --git a/src/Components/MSBuild.fs b/src/Components/MSBuild.fs index 8008f7b9..aa7eff4b 100644 --- a/src/Components/MSBuild.fs +++ b/src/Components/MSBuild.fs @@ -15,11 +15,21 @@ module node = Node.Api module MSBuild = - let outputChannel = window.createOutputChannel "msbuild" + let outputChannel = window.createOutputChannel "Ionide: MSBuild" let private logger = ConsoleAndOutputChannelLogger(Some "msbuild", Level.DEBUG, Some outputChannel, Some Level.DEBUG) + let private dotnetBinary () = + LanguageService.dotnet () + |> Promise.bind (function + | Some msbuild -> Promise.lift msbuild + | None -> + Promise.reject ( + exn + "dotnet SDK not found. Please install it from the [Dotnet SDK Download Page](https://www.microsoft.com/net/download)" + )) + let invokeMSBuild project target = let autoshow = let cfg = workspace.getConfiguration () @@ -29,16 +39,7 @@ module MSBuild = let executeWithHost () = promise { - let! msbuildPath = - LanguageService.dotnet () - |> Promise.bind (function - | Some msbuild -> Promise.lift msbuild - | None -> - Promise.reject ( - exn - "dotnet SDK not found. Please install it from the [Dotnet SDK Download Page](https://www.microsoft.com/net/download)" - )) - + let! msbuildPath = dotnetBinary () let cmd = ResizeArray("msbuild" :: command) logger.Info("invoking msbuild from %s on %s for target %s", msbuildPath, project, target) @@ -63,70 +64,119 @@ module MSBuild = ) |> Promise.ofThenable + let msbuildTasksFilter: TaskFilter = + let f = createEmpty + f.``type`` <- Some "msbuild" + f + /// discovers the project that the active document belongs to and builds that - let buildCurrentProject target = - logger.Debug("discovering project") - - match window.activeTextEditor.Value.document with - | Document.FSharp - | Document.CSharp - | Document.VB -> - let currentProject = - Project.getLoaded () - |> Seq.where (fun p -> - p.Files - |> Seq.exists (String.endWith window.activeTextEditor.Value.document.fileName)) - |> Seq.tryHead - - match currentProject with - | Some p -> - logger.Debug("found project %s", p.Project) - invokeMSBuild p.Project target - | None -> + let buildCurrentProject (target: string) = + promise { + logger.Debug("discovering project for current file") + + match window.activeTextEditor.Value.document with + | Document.FSharp + | Document.CSharp + | Document.VB -> + let currentProject = + Project.getLoaded () + |> Seq.where (fun p -> + p.Files + |> Seq.exists (String.endWith window.activeTextEditor.Value.document.fileName)) + |> Seq.tryHead + + match currentProject with + | Some p -> + logger.Debug("found project %s", p.Project) + let projectFile = path.basename p.Project + let! msbuildTasks = tasks.fetchTasks (msbuildTasksFilter) + + let expectedGroup = + match target with + | "Build" -> Some vscode.TaskGroup.Build + | "Rebuild" -> Some vscode.TaskGroup.Rebuild + | "Clean" -> Some vscode.TaskGroup.Clean + | _ -> None + + if expectedGroup = None then return () + + let t = + msbuildTasks + |> Seq.tryFind (fun t -> t.name = projectFile && t.group = expectedGroup) + + match t with + | None -> logger.Debug("no task found for target %s for project %s", target, projectFile) + | Some t -> + let! ex = tasks.executeTask (t) + return () + | None -> + logger.Debug( + "could not find a project that contained the file %s", + window.activeTextEditor.Value.document.fileName + ) + + return () + | _ -> logger.Debug( - "could not find a project that contained the file %s", - window.activeTextEditor.Value.document.fileName + "I don't know how to handle a project of type %s", + window.activeTextEditor.Value.document.languageId ) - Promise.empty - | _ -> - logger.Debug( - "I don't know how to handle a project of type %s", - window.activeTextEditor.Value.document.languageId - ) - - Promise.empty + return () + } /// prompts the user to choose a project let pickProject placeHolder = logger.Debug "pick project" - let projects = Project.getAll () |> ResizeArray + let projects = Project.getLoaded () |> ResizeArray if projects.Count = 0 then None |> Promise.lift else promise { + let items = projects |> Seq.map (fun p -> path.basename p.Project) |> ResizeArray let opts = createEmpty opts.placeHolder <- Some placeHolder - let! chosen = window.showQuickPick (projects |> U2.Case1, opts) + let! chosen = window.showQuickPick (U2.Case1 items, opts) logger.Debug("user chose project %s", chosen) - return chosen + match chosen with + | None -> return None + | Some chosen -> + return projects |> Seq.tryFind (fun p -> path.basename p.Project = chosen) } /// prompts the user to choose a project (if not specified) and builds that project - let buildProject target projOpt = + let buildProject target = promise { logger.Debug "building project" - let! chosen = - match projOpt with - | None -> pickProject "Project to build" - | Some h -> Some h |> Promise.lift + let! chosen = pickProject "Select a project to build" + let! msbuildTasks = tasks.fetchTasks msbuildTasksFilter return! match chosen with - | None -> { Code = Some 0; Signal = None } |> Promise.lift - | Some proj -> invokeMSBuild proj target + | None -> Promise.lift () + | Some proj -> promise { + let projectFile = path.basename proj.Project + let expectedGroup = + match target with + | "Build" -> Some vscode.TaskGroup.Build + | "Rebuild" -> Some vscode.TaskGroup.Rebuild + | "Clean" -> Some vscode.TaskGroup.Clean + | _ -> None + + if expectedGroup = None then return () + + let t = + msbuildTasks + |> Seq.tryFind (fun t -> t.name = projectFile && t.group = expectedGroup) + + match t with + | None -> logger.Debug("no task found for target %s for project %s", target, projectFile) + | Some t -> + let! ex = tasks.executeTask (t) + return () + } } let buildProjectPath target (project: Project) = invokeMSBuild project.Project target @@ -169,16 +219,6 @@ module MSBuild = messageLoop ()) - let private restoreProjectCmd (projOpt: string option) = - buildProject "Restore" projOpt - |> Promise.onSuccess (fun exit -> - let failed = exit.Code <> Some 0 - - match failed, projOpt with - | false, Some p -> Project.load true p |> unbox - | true, Some p -> logger.Error("Restore of %s failed with code %i, signal %s", p, exit.Code, exit.Signal) - | _ -> ()) - let restoreProjectAsync (path: string) = restoreMailBox.Post( path, @@ -196,8 +236,27 @@ module MSBuild = let buildSolution target sln = promise { - let! _ = invokeMSBuild sln target - return () + let solutionFile = path.basename sln + let! msbuildTasks = tasks.fetchTasks msbuildTasksFilter + + let expectedGroup = + match target with + | "Build" -> Some vscode.TaskGroup.Build + | "Rebuild" -> Some vscode.TaskGroup.Rebuild + | "Clean" -> Some vscode.TaskGroup.Clean + | _ -> None + + if expectedGroup = None then return () + + let t = + msbuildTasks + |> Seq.tryFind (fun t -> t.name = "solution" && t.group = expectedGroup) + + match t with + | None -> logger.Debug("no task found for target %s for solution %s", target, solutionFile) + | Some t -> + let! ex = tasks.executeTask (t) + return () } let buildCurrentSolution target = @@ -210,6 +269,163 @@ module MSBuild = window.showWarningMessage ("Solution not loaded") |> ignore + type MSBuildTask = Task + + // type ShellExecutionStatic = + // abstract Create() + + let invokeDotnet dotnetPath subcommand args cwd env : ShellExecution = + let args = + (subcommand :: args) + |> Seq.map U2.Case1 + |> ResizeArray + + let opts = createEmpty + opts.cwd <- Some cwd + let envs = createEmpty + env |> Map.iter (fun k v -> envs?k <- v) + vscode.ShellExecution.Create(U2.Case1 dotnetPath, args, opts) + + let msbuildTaskDef: TaskDefinition = + let t = createEmpty + t?``type`` <- "msbuild" + t + + let private restoreProjectCmd () = + promise { + logger.Debug "building project" + + let! chosen = pickProject "Select a project to restore" + match chosen with + | None -> return () + | Some project -> + let projectFile = path.basename project.Project + let projectDir = path.dirname project.Project + let! dotnet = dotnetBinary () + let execution = invokeDotnet dotnet "restore" [] projectDir Map.empty + + let detailString = $"Restore the {projectFile} project using `dotnet restore`" + + let restoreTask = + vscode.Task.Create( + msbuildTaskDef, + U2.Case2 TaskScope.Workspace, + $"{projectFile}", + "Restore", + U3.Case2 execution, + U2.Case1 "$msCompile", + group = Some vscode.TaskGroup.Build, + detail = Some detailString + ) + + let! ex = tasks.executeTask restoreTask + return () + } + + let perProjectTasks = + [ "build", [], vscode.TaskGroup.Build, "Build" + "clean", [], vscode.TaskGroup.Clean, "Clean" + "msbuild", [ "/t:Rebuild" ], vscode.TaskGroup.Rebuild, "Rebuild" ] + + let buildTaskListForProject (p: Project) : JS.Promise = + promise { + let projectName = path.basename p.Project + let projectDir = path.dirname p.Project + let! dotnet = dotnetBinary () + + return + seq { + for (command, args, group, verb) in perProjectTasks do + let execution = invokeDotnet dotnet command args projectDir Map.empty + let cmdline = "dotnet" :: command :: args |> String.concat " " + let detailString = $"{verb} the {projectName} project using {cmdline}" + + let t = + vscode.Task.Create( + msbuildTaskDef, + U2.Case2 TaskScope.Workspace, + $"{projectName}", + verb, + U3.Case2 execution, + U2.Case1 "$msCompile", + group = Some group, + detail = Some detailString + ) + + yield t + } + } + + let buildTaskListForSolution (s: WorkspacePeekFound) : JS.Promise = + promise { + match s with + | WorkspacePeekFound.Directory _ -> return Seq.empty + | WorkspacePeekFound.Solution ({ Path = p }) -> + let! dotnet = dotnetBinary () + let solutionDir = path.dirname p + let solutionName = path.basename p + + return + seq { + for (command, args, group, verb) in perProjectTasks do + let execution = + invokeDotnet dotnet command (solutionName :: args) solutionDir Map.empty + + let cmdline = "dotnet" :: command :: args |> String.concat " " + let detailString = $"{verb} the {solutionName} solution using {cmdline}" + + let t = + vscode.Task.Create( + msbuildTaskDef, + U2.Case2 TaskScope.Workspace, + "solution", + verb, + U3.Case2 execution, + U2.Case1 "$msCompile", + group = Some group, + detail = Some detailString + ) + + yield t + } + + } + + let traverse (p: ResizeArray>) : JS.Promise option> = + promise { + let! outputs = Promise.all (Array.ofSeq p) + return Some(ResizeArray(outputs)) + } + + let msbuildBuildTaskProvider = + { new TaskProvider with + override x.provideTasks(token: CancellationToken) = + logger.Info "providing tasks" + + let projectTasks = + Project.getLoaded () + |> Seq.map buildTaskListForProject + + let solutionTasks = + Project.getLoadedSolution () + |> Option.map buildTaskListForSolution + |> Option.toList + + let tasks = + projectTasks + |> Seq.append solutionTasks + |> ResizeArray + |> traverse + |> Promise.map (function + | None -> None + | Some tasks -> tasks |> Seq.concat |> ResizeArray |> Some) + |> Promise.toThenable + + ProviderResult.Some(U2.Case2 tasks) + + override x.resolveTask(t: MSBuildTask, token: CancellationToken) = + logger.Info("resolving task %s", t.name) + ProviderResult.Some(U2.Case1 t) } let activate (context: ExtensionContext) = let unlessIgnored (path: string) f = @@ -250,6 +466,8 @@ module MSBuild = fun _ -> f p + tasks.registerTaskProvider ("msbuild", msbuildBuildTaskProvider) + |> context.Subscribe registerCommand "MSBuild.buildCurrent" (fun _ -> buildCurrentProject "Build") registerCommand "MSBuild.rebuildCurrent" (fun _ -> buildCurrentProject "Rebuild") @@ -259,7 +477,14 @@ module MSBuild = registerCommand "MSBuild.rebuildCurrentSolution" (fun _ -> buildCurrentSolution "Rebuild") registerCommand "MSBuild.cleanCurrentSolution" (fun _ -> buildCurrentSolution "Clean") - registerCommand2 "MSBuild.buildSelected" (typedMsbuildCmd (buildProject "Build")) - registerCommand2 "MSBuild.rebuildSelected" (typedMsbuildCmd (buildProject "Rebuild")) - registerCommand2 "MSBuild.cleanSelected" (typedMsbuildCmd (buildProject "Clean")) - registerCommand2 "MSBuild.restoreSelected" (typedMsbuildCmd restoreProjectCmd) + commands.registerCommand("MSBuild.buildSelected", fun _ -> buildProject "Build" |> Promise.map box |> box |> Some) + |> context.Subscribe + + commands.registerCommand("MSBuild.buildSelected", fun _ -> buildProject "Rebuild" |> Promise.map box |> box |> Some) + |> context.Subscribe + + commands.registerCommand("MSBuild.buildSelected", fun _ -> buildProject "Clean" |> Promise.map box |> box |> Some) + |> context.Subscribe + + commands.registerCommand("MSBuild.restoreSelected", fun _ -> restoreProjectCmd () |> Promise.map box |> box |> Some) + |> context.Subscribe diff --git a/src/Core/Utils.fs b/src/Core/Utils.fs index b4d1705a..9c3ce23b 100644 --- a/src/Core/Utils.fs +++ b/src/Core/Utils.fs @@ -433,7 +433,7 @@ module VSCodeExtension = module Environment = /// expand any env variables in a string according to /// .NET's rules - that is any %-encoded name is an env var - [ process.env[n])")>] + [ process.env[n])")>] let expand (s: string): string = jsNative []