# .NET Solution Launcher Startup project and solution configuration in the status bar, with build, debug, run and hot reload that actually use them. Built for [DotRush](https://github.com/JaneySprings/DotRush). ``` $(project) MyGame.Editor $(settings-gear) Test | x64 $(debug-alt) $(flame) $(database) 4/4 ``` - The **project item** shows which `.csproj` is the startup project, and picks another one from the executables in the solution (Runtime is a library, so it is not offered). - The **configuration item** shows the *solution* configuration — `Debug | x64`, `Test | x64`, `Release | Any CPU` — exactly as `MyGame.sln` lists them. - The **debug button** builds with that configuration and launches the startup project. - The **flame** starts it under `dotnet watch` with hot reload instead. - The **database counter** says how many projects of the solution DotRush's language server has actually loaded. - The **database counter** says how many projects of the solution DotRush's language server has actually loaded. ## Why, when DotRush already has a status bar item DotRush shows `Debug | net10.0`. Two things are missing from that: 1. **The project name.** It is in the tooltip only. With three executables in the solution (Builder, Editor, and whatever comes next) that is the thing you need to see. 2. **The platform.** DotRush's build task runs `dotnet build -p:Configuration=X` with no `-p:Platform`. MSBuild then defaults to `AnyCPU`, and every property group conditioned on `'$(Configuration)|$(Platform)' == 'Debug|x64'` is skipped. In this solution that is not cosmetic: | `MyGame.Runtime`, Configuration=Debug | `DefineConstants` | | --- | --- | | without `-p:Platform` (what DotRush runs) | `TRACE;DEBUG` | | with `-p:Platform=x64` | `TRACE;LOG_INFO;PROFILING;DEBUG` | So logging and profiling silently vanish, `Optimize` is never set for Test/Release, and `MyGame.Builder` loses `AllowUnsafeBlocks` and fails to compile. DotRush also cannot be *told* a configuration from outside — its selection lives in its own workspace state — so this extension owns the build rather than wrapping `dotrush: Build`. The startup *project* is synced with DotRush in both directions (`dotnetSolution.syncDotRush`), so its test explorer, `launchSettings.json` lookup and `${command:dotrush.activeProjectPath}` all agree with what the status bar says. ## What a build does `dotnetSolution.buildScope` decides (default `solution`): | Scope | Command | Notes | | --- | --- | --- | | `solution` | `dotnet build MyGame.sln -c Test -p:Platform=x64` | Like Visual Studio's *Build Solution*. MSBuild maps every project through the `.sln`: MyGame.* build as `Test\|x64`, MoonWorks as `Debug\|Any CPU`, and the Editor → Builder `ProjectDependencies` entry is honoured, so the Builder exists before the Editor's post-build step needs it. | | `project` | `dotnet build MyGame.Editor.csproj -c Test -p:Platform=x64` | Only the startup project and its `ProjectReference`s. Faster, but solution-only dependencies are ignored. | Rebuild adds `--no-incremental`; Clean runs `dotnet clean`. Two more targets use the same selection: | Target | Command | Notes | | --- | --- | --- | | `publish` | `dotnet publish MyGame.Editor.csproj -c Release -p:Platform=x64 [-r win-x64]` | Always the startup project. `dotnetSolution.publish.runtime` sets `-r`, which `PublishAot` needs; `publish.args` adds the rest | | `test` | `dotnet test MyGame.sln -c Test -p:Platform=x64` | Follows `buildScope`; `dotnetSolution.test.args` adds filters or `--no-build` | All five are tasks of type `dotnet-solution`, so they show up under *Run Task* and can be used as `preLaunchTask`. When one fails the Problems panel opens (`dotnetSolution.showProblemsOnFailure`). ## What a launch does `program` comes from MSBuild, not from guessing: the extension evaluates the startup project with the mapped configuration and platform (`dotnet msbuild -getProperty:TargetPath …`), which is the only way to get it right here — `OutDir` comes from `Directory.Build.props` and `AppendTargetFrameworkToOutputPath` is off. The result is cached until a project or solution file changes. The apphost `.exe` is launched when the project produces one, since vsdbg wants an executable; otherwise it runs `dotnet `. DotRush's debug configuration provider still runs after this one and fills in `justMyCode` and symbol options. ### Arguments, environment, working directory Builder and Editor want different arguments, so these are layered, most specific wins: 1. `dotnetSolution.launch.projects`, keyed by project name: ```jsonc "dotnetSolution.launch.projects": { "MyGame.Builder": { "args": ["-build", "-resourcePath", "Resources"], "cwd": "../MyGame.Editor" }, "MyGame.Editor": { "console": "integratedTerminal" } } ``` `cwd` is relative to the project folder. `profile` names a launchSettings.json profile. 2. The global `dotnetSolution.launch.args` / `.env` / `.cwd` / `.console`. 3. `Properties/launchSettings.json` next to the project: `commandLineArgs`, `environmentVariables` and `workingDirectory` of the first `"commandName": "Project"` profile, or the one named by `dotnetSolution.launch.profile`. 4. Otherwise no arguments and `TargetDir` as the working directory. The same layering applies to F5 through the launch.json entry: its `args` stay empty in the file and are filled in at launch time. ## Hot reload The third way to start the startup project, next to Debug and Run, and a rewrite of the earlier `dotnet-hot-reload` extension now that the selection exists to build on: ``` dotnet watch run --project MyGame.Editor.csproj -c Debug --property:Platform=x64 -- ``` Same project, configuration, platform, arguments, environment and working directory as F5. It runs in an integrated terminal named after the project, and the status bar item next to the debug button follows the watcher's output: *watching*, *applied*, *failed*, *restart needed*. Save a file and the change is applied; the flame in the terminal's title is the same session. | Action | How | | --- | --- | | Start | The `$(flame)` status bar item (icon only; it gains a word — *failed*, *restart needed* — only when something needs you), *.NET Solution: Run Startup Project with Hot Reload*, or `Ctrl+Alt+F5` | | Apply, restart, attach, stop | Click the status bar item while it runs — one menu for all of them | | Restart the application | Also `Ctrl+R` inside the watch terminal | **The debugger is not attached by default.** That is the biggest change from the old extension, and deliberate. `dotnet watch` replaces the application process on every rude edit, restart or crash, and each of those ends an attached debug session; keeping a debugger attached across that meant guessing whether an ended session was a stop or a swap, racing the outgoing process, and giving up on crash loops. Now *Attach Debugger* finds the application below the watcher (by assembly name, so MSBuild is never picked) and attaches when you ask. If the process is later replaced while the watcher still runs, one notification says so and offers to re-attach. While attached, the flame and restart buttons also appear in the debug toolbar. **Rude edits** — changes hot reload cannot apply — follow `dotnetSolution.hotReload.rudeEdit`: | Value | Behaviour | | --- | --- | | `restart` (default) | dotnet watch restarts the application on its own | | `ask` | A notification offers *Restart* / *Keep running*; the answer goes to dotnet watch's console question | | `warn` | Keeps the old code running and shows a warning with a *Restart* button | **Optimised builds cannot hot reload.** SDK 10's `dotnet watch` refuses when `Optimize` is true and restarts on every change instead. Test and Release set it here, so starting hot reload under those asks whether to switch to Debug first. **Solution build first.** `dotnet watch` only builds the project it runs. With `dotnetSolution.hotReload.buildSolutionFirst` (default on) the normal solution build runs before the watcher starts, so the Editor's post-build step finds the Builder even on a clean checkout; the watcher's own build is then incremental. ## Language server status DotRush shows a spinner while it loads the workspace and nothing afterwards, so when Find References comes back empty there is no way to tell "no references" from "not indexed". DotRush does raise one `projectLoaded` event per project through its exports; the `$(database) 3/4` item counts those against the solution in the status bar and turns warning-coloured in two cases: - the **startup project was never loaded**, so IntelliSense and references miss it; - projects were loaded from **outside the selected solution** (`+1`), which is what a `dotrush.roslyn.projectOrSolutionFiles` pointing at another checkout looks like. The tooltip lists loaded and missing projects. Clicking offers *Reload Workspace* (which also resets the count), DotRush's solution picker, and its output channel. The `dotnetSolution.languageServerStatus` command returns the same data for other extensions; *colored-references* uses it to say *why* a search found nothing. The count only includes events raised while this extension was listening, so a window where DotRush finished before this extension activated shows 0 until a reload. ## Usage | Action | How | | --- | --- | | Pick the startup project | Click the project name (`Ctrl+Alt+P`), or right-click a `.csproj` → *Set as Startup Project (.NET Solution)*. The explorer marks it with ▶ | | Pick the configuration | Click `Debug \| x64` (`Ctrl+Alt+C`). Entries the solution does not build the project under are marked with `$(warning)` | | Build / Rebuild / Clean | *.NET Solution: Build* etc., or the `dotnet-solution` tasks. `Ctrl+Shift+B` until a launch.json entry exists, after that VS Code's default build task | | Publish / Test | *.NET Solution: Publish Startup Project*, *.NET Solution: Run Tests* | | Debug | The `$(debug-alt)` button, *.NET Solution: Debug Startup Project*, or F5 | | Hot reload | The `$(flame)` item, or `Ctrl+Alt+F5`. See above | | Run without debugging | *.NET Solution: Run Startup Project (without debugging)*, or `Ctrl+F5` | | Several `.sln` files | *.NET Solution: Select Solution*, or `dotnetSolution.solution` | ### F5 Without a launch.json entry, `F5`, `Ctrl+F5` and `Ctrl+Shift+B` are bound to this extension's debug, run and build commands (only while a solution is loaded and no debug session is running). Once the entry below exists those keys go back to VS Code's own handling, which then uses the entry — same result, but editable in launch.json. The first time it sees a solution the extension offers to write a `launch.json` entry. Later, run *.NET Solution: Create launch.json and tasks.json entries* from the command palette, or pick it at the bottom of the startup-project list (click the project name): ```jsonc { "name": ".NET Solution: Debug startup project", "type": "coreclr", "request": "launch", "program": "${command:dotnetSolution.activeProgram}", "cwd": "${command:dotnetSolution.activeCwd}", "preLaunchTask": "dotnet-solution: Build" } ``` The same entry is offered dynamically in the *Run and Debug* dropdown even without a `launch.json`. It replaces the DotRush template's `${command:dotrush.activeTargetPath}` + `preLaunchTask: "dotrush: Build"` pair, which is the one that builds without a platform. ### Variables for your own launch.json / tasks.json | `${command:…}` | Value | | --- | --- | | `dotnetSolution.activeProgram` | apphost `.exe`, or the `.dll` when there is none | | `dotnetSolution.activeTargetPath` | the built assembly | | `dotnetSolution.activeTargetDir` | its directory | | `dotnetSolution.activeCwd` | the working directory after the layering above | | `dotnetSolution.activeProjectPath`, `activeProjectName` | the startup project | | `dotnetSolution.activeSolutionPath` | the solution | | `dotnetSolution.activeConfiguration`, `activePlatform` | solution configuration, e.g. `Test`, `Any CPU` | | `dotnetSolution.activeProjectConfiguration`, `activeProjectPlatform` | what the project gets, e.g. `Test`, `AnyCPU` | ## Settings | Setting | Default | | | --- | --- | --- | | `dotnetSolution.solution` | `""` | Solution to use; empty picks the shallowest, then the largest | | `dotnetSolution.buildScope` | `solution` | See above | | `dotnetSolution.targetFramework` | `""` | For `TargetFrameworks` projects; empty takes the first | | `dotnetSolution.launch.args` / `.env` / `.cwd` / `.console` | | Passed to the launched application | | `dotnetSolution.launch.projects` | `{}` | The same, per project name; wins over the global ones | | `dotnetSolution.launch.profile` | `""` | launchSettings.json profile to read | | `dotnetSolution.publish.runtime` / `.args` | `""` / `[]` | `-r` and extra arguments for publish | | `dotnetSolution.test.args` | `[]` | Extra arguments for test | | `dotnetSolution.showProblemsOnFailure` | `true` | Open Problems when a task fails | | `dotnetSolution.hotReload.rudeEdit` | `restart` | `restart`, `ask` or `warn` | | `dotnetSolution.hotReload.buildSolutionFirst` | `true` | Solution build before `dotnet watch` | | `dotnetSolution.hotReload.watchArgs` | `[]` | Extra arguments for `dotnet watch` itself | | `dotnetSolution.debugType` | `coreclr` | | | `dotnetSolution.syncDotRush` | `true` | Push the startup project to DotRush and follow its changes | | `dotnetSolution.syncDotRushWorkspaceProperties` | `false` | Write `Configuration=…;Platform=…` into `dotrush.roslyn.workspaceProperties` so IntelliSense sees the same `DefineConstants`. Off because DotRush reloads its workspace on every change | | `dotnetSolution.additionalBuildArguments` | `[]` | Appended to every build | `dotrush.roslyn.dotnetSdkDirectory` and `dotrush.msbuild.additionalEnvironment` are honoured, so both extensions use the same SDK. ## Tips - DotRush's own `Debug | net10.0` item can be hidden: right-click the status bar and untick *DotRush*. Nothing else depends on it once this extension drives the build. - The status bar goes to a spinner while a build runs; the build output is in the terminal and the *Problems* panel (`$msCompile`). - The *.NET Solution* output channel logs every `dotnet` invocation. ## Development ``` npm install npm run compile npm run test:unit # parser tests, against D:\Projects\MyGame when present npm test # launches VS Code on that folder (SOLUTION_TEST_FOLDER overrides) ```