# .NET Solution Launcher Startup project and solution configuration in the status bar, with build and debug that actually use them. Built for [DotRush](https://github.com/JaneySprings/DotRush), next to the `.NET Hot Reload` extension in this repo. ``` $(project) MyGame.Editor $(settings-gear) Test | x64 $(debug-alt) ``` - 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. ## 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. ## 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 | | 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.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) ```