diff --git a/.github/workflows/ci-cd.yml b/.github/workflows/ci-cd.yml new file mode 100644 index 0000000..d7e7303 --- /dev/null +++ b/.github/workflows/ci-cd.yml @@ -0,0 +1,108 @@ +name: CI-CD + +# Concurrency control ensures that only one instance of the workflow runs at a time for a given reference. +# This prevents multiple runs from interfering with each other. +# Reference: https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/control-the-concurrency-of-workflows-and-jobs +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +on: + push: + branches: + - main + - development + + pull_request: + branches: + - main + - development + + # To allow manual execution + workflow_dispatch: + + +env: + # Path to the solution file relative to the root of the project. + SOLUTION_FILE_PATH: ./src/RaspberryDebugger.sln + + PROJECT_FILE_PATH: ./src/RaspberryDebugger/RaspberryDebugger.csproj + + # Configuration type to build. + # You can convert this to a build matrix if you need coverage of multiple configuration types. + # https://docs.github.com/actions/learn-github-actions/managing-complex-workflows#using-a-build-matrix + BUILD_PLATFORM: 'Any CPU' + BUILD_PLATFORM_FOR_PROJECTS: 'AnyCPU' + +permissions: + contents: read + +jobs: + # build_Linux: + build_Windows: + runs-on: [windows-2022] + + strategy: + matrix: + configuration: [Release] + platform: [Any CPU] + + steps: + - name: 📂 Checkout code + uses: actions/checkout@v4 + with: + path: './src' + # Fetch depth needs to be set to 0 to use Nerdbank.GitVersioning + fetch-depth: 0 + + - name: 📂 Checkout NeonSDK + uses: actions/checkout@v4 + with: + repository: bakerhillpins/neonSDK + path: './neonSDK' + ref: refs/heads/Development + + # Install the .NET Core workload + - name: 🤖 Install .NET Core + uses: actions/setup-dotnet@v4 + with: + global-json-file: ./src/global.json + env: + DOTNET_INSTALL_DIR: "${{ runner.temp }}\\dotnet" + + # Add MSBuild to the PATH: https://github.com/microsoft/setup-msbuild + - name: 🤖 Setup MSBuild.exe + uses: microsoft/setup-msbuild@v2 + + # Restore the application to populate the obj folder with RuntimeIdentifiers + - name: ⚙️ Restore the application + run: > + msbuild + "${{ env.SOLUTION_FILE_PATH }}" + -verbosity:minimal + /t:Restore + /p:Configuration="${{ env.Configuration }}" + + env: + Configuration: ${{ matrix.configuration }} + + # Create the app package by building and packaging the Windows Application Packaging project + - name: 🤖 Create the app package + run: > + msbuild + -verbosity:minimal + "${{ env.SOLUTION_FILE_PATH }}" + /p:Configuration="${{ env.Configuration }}" + + env: + Configuration: ${{ matrix.configuration }} + + - name: 📤 Store Artifact + uses: actions/upload-artifact@v4 + with: + name: RaspberryDebugger.vsix + path: ./src/RaspberryDebugger/bin/${{ env.Configuration }}/RaspberryDebugger.vsix + + env: + Configuration: ${{ matrix.configuration }} + diff --git a/RaspberryDebugger.sln b/RaspberryDebugger.sln index 1cf3839..5320cf0 100644 --- a/RaspberryDebugger.sln +++ b/RaspberryDebugger.sln @@ -1,67 +1,97 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 17 -VisualStudioVersion = 17.2.32505.173 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "GitHub", "GitHub", "{177F60A0-B1B9-45D1-9CB1-8F06A6ED2AB5}" - ProjectSection(SolutionItems) = preProject - .gitignore = .gitignore - buildenv.cmd = buildenv.cmd - LICENSE = LICENSE - README.md = README.md - EndProjectSection -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RaspberryDebugger", "RaspberryDebugger\RaspberryDebugger.csproj", "{80D6A0E0-A145-488C-976D-CE34A16D8533}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Doc", "Doc", "{5A14B2C0-0606-433A-AE9C-9E33C7B2BA27}" - ProjectSection(SolutionItems) = preProject - Doc\DEVELOPER.md = Doc\DEVELOPER.md - MAINTAIN.md = MAINTAIN.md - Doc\RELEASE-TEMPLATE.md = Doc\RELEASE-TEMPLATE.md - Doc\RELEASE.md = Doc\RELEASE.md - EndProjectSection -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Images", "Images", "{642CFDE5-32E6-46EB-B79E-8E380CCE0237}" - ProjectSection(SolutionItems) = preProject - Doc\Images\RaspberryDebugSettings.png = Doc\Images\RaspberryDebugSettings.png - EndProjectSection -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ToolBin", "ToolBin", "{15A5F928-90D4-421A-850D-8A09842DA012}" - ProjectSection(SolutionItems) = preProject - ToolBin\archive.cmd = ToolBin\archive.cmd - ToolBin\archive.ps1 = ToolBin\archive.ps1 - ToolBin\builder.cmd = ToolBin\builder.cmd - ToolBin\builder.ps1 = ToolBin\builder.ps1 - ToolBin\openssl.exe = ToolBin\openssl.exe - ToolBin\PathTool.exe = ToolBin\PathTool.exe - EndProjectSection -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Test", "Test", "{21D869D2-B357-485A-89C0-6A18B8886432}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Blinky", "Blinky", "{C7EA532F-BE47-4993-9599-66168340E980}" - ProjectSection(SolutionItems) = preProject - Test\Blinkie\Blinky.sln = Test\Blinkie\Blinky.sln - EndProjectSection -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {80D6A0E0-A145-488C-976D-CE34A16D8533}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {80D6A0E0-A145-488C-976D-CE34A16D8533}.Debug|Any CPU.Build.0 = Debug|Any CPU - {80D6A0E0-A145-488C-976D-CE34A16D8533}.Release|Any CPU.ActiveCfg = Release|Any CPU - {80D6A0E0-A145-488C-976D-CE34A16D8533}.Release|Any CPU.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(NestedProjects) = preSolution - {642CFDE5-32E6-46EB-B79E-8E380CCE0237} = {5A14B2C0-0606-433A-AE9C-9E33C7B2BA27} - {C7EA532F-BE47-4993-9599-66168340E980} = {21D869D2-B357-485A-89C0-6A18B8886432} - EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {52E13CDA-74D0-400C-83D8-417CC3C113D6} - EndGlobalSection -EndGlobal + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.2.32505.173 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "GitHub", "GitHub", "{177F60A0-B1B9-45D1-9CB1-8F06A6ED2AB5}" + ProjectSection(SolutionItems) = preProject + .gitignore = .gitignore + buildenv.cmd = buildenv.cmd + LICENSE = LICENSE + README.md = README.md + EndProjectSection +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RaspberryDebugger", "RaspberryDebugger\RaspberryDebugger.csproj", "{80D6A0E0-A145-488C-976D-CE34A16D8533}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Doc", "Doc", "{5A14B2C0-0606-433A-AE9C-9E33C7B2BA27}" + ProjectSection(SolutionItems) = preProject + Doc\DEVELOPER.md = Doc\DEVELOPER.md + MAINTAIN.md = MAINTAIN.md + Doc\RELEASE-TEMPLATE.md = Doc\RELEASE-TEMPLATE.md + Doc\RELEASE.md = Doc\RELEASE.md + EndProjectSection +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Images", "Images", "{642CFDE5-32E6-46EB-B79E-8E380CCE0237}" + ProjectSection(SolutionItems) = preProject + Doc\Images\RaspberryDebugSettings.png = Doc\Images\RaspberryDebugSettings.png + EndProjectSection +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ToolBin", "ToolBin", "{15A5F928-90D4-421A-850D-8A09842DA012}" + ProjectSection(SolutionItems) = preProject + ToolBin\archive.cmd = ToolBin\archive.cmd + ToolBin\archive.ps1 = ToolBin\archive.ps1 + ToolBin\builder.cmd = ToolBin\builder.cmd + ToolBin\builder.ps1 = ToolBin\builder.ps1 + ToolBin\openssl.exe = ToolBin\openssl.exe + ToolBin\PathTool.exe = ToolBin\PathTool.exe + EndProjectSection +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Test", "Test", "{21D869D2-B357-485A-89C0-6A18B8886432}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Blinky", "Blinky", "{C7EA532F-BE47-4993-9599-66168340E980}" + ProjectSection(SolutionItems) = preProject + Test\Blinkie\Blinky.sln = Test\Blinkie\Blinky.sln + EndProjectSection +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Neon.BuildInfo", "..\neonSDK\Lib\Neon.BuildInfo\Neon.BuildInfo.csproj", "{62AEE7BF-2F8B-297E-4BDA-DB9289DE49AA}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Neon.Common", "..\neonSDK\Lib\Neon.Common\Neon.Common.csproj", "{7B3C9089-7DCC-90DC-A824-B0EECD7C511C}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Neon.Cryptography", "..\neonSDK\Lib\Neon.Cryptography\Neon.Cryptography.csproj", "{C0E0FD22-0AB6-6B0B-8571-C5F4D6EDD7B8}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Neon.SSH", "..\neonSDK\Lib\Neon.SSH\Neon.SSH.csproj", "{030C5DD8-990D-0DD3-2886-02CB01C171CB}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "workflows", "workflows", "{02EA681E-C7D8-13C7-8484-4AC65E1B71E8}" + ProjectSection(SolutionItems) = preProject + .github\workflows\ci-cd.yml = .github\workflows\ci-cd.yml + EndProjectSection +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {80D6A0E0-A145-488C-976D-CE34A16D8533}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {80D6A0E0-A145-488C-976D-CE34A16D8533}.Debug|Any CPU.Build.0 = Debug|Any CPU + {80D6A0E0-A145-488C-976D-CE34A16D8533}.Release|Any CPU.ActiveCfg = Release|Any CPU + {80D6A0E0-A145-488C-976D-CE34A16D8533}.Release|Any CPU.Build.0 = Release|Any CPU + {62AEE7BF-2F8B-297E-4BDA-DB9289DE49AA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {62AEE7BF-2F8B-297E-4BDA-DB9289DE49AA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {62AEE7BF-2F8B-297E-4BDA-DB9289DE49AA}.Release|Any CPU.ActiveCfg = Release|Any CPU + {62AEE7BF-2F8B-297E-4BDA-DB9289DE49AA}.Release|Any CPU.Build.0 = Release|Any CPU + {7B3C9089-7DCC-90DC-A824-B0EECD7C511C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7B3C9089-7DCC-90DC-A824-B0EECD7C511C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7B3C9089-7DCC-90DC-A824-B0EECD7C511C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7B3C9089-7DCC-90DC-A824-B0EECD7C511C}.Release|Any CPU.Build.0 = Release|Any CPU + {C0E0FD22-0AB6-6B0B-8571-C5F4D6EDD7B8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C0E0FD22-0AB6-6B0B-8571-C5F4D6EDD7B8}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C0E0FD22-0AB6-6B0B-8571-C5F4D6EDD7B8}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C0E0FD22-0AB6-6B0B-8571-C5F4D6EDD7B8}.Release|Any CPU.Build.0 = Release|Any CPU + {030C5DD8-990D-0DD3-2886-02CB01C171CB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {030C5DD8-990D-0DD3-2886-02CB01C171CB}.Debug|Any CPU.Build.0 = Debug|Any CPU + {030C5DD8-990D-0DD3-2886-02CB01C171CB}.Release|Any CPU.ActiveCfg = Release|Any CPU + {030C5DD8-990D-0DD3-2886-02CB01C171CB}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {642CFDE5-32E6-46EB-B79E-8E380CCE0237} = {5A14B2C0-0606-433A-AE9C-9E33C7B2BA27} + {C7EA532F-BE47-4993-9599-66168340E980} = {21D869D2-B357-485A-89C0-6A18B8886432} + {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} = {177F60A0-B1B9-45D1-9CB1-8F06A6ED2AB5} + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {52E13CDA-74D0-400C-83D8-417CC3C113D6} + EndGlobalSection +EndGlobal diff --git a/RaspberryDebugger/Commands/DebugStartCommand.cs b/RaspberryDebugger/Commands/DebugStartCommand.cs index 476c388..33ff91c 100644 --- a/RaspberryDebugger/Commands/DebugStartCommand.cs +++ b/RaspberryDebugger/Commands/DebugStartCommand.cs @@ -1,4 +1,4 @@ -//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- // FILE: DebugStartCommand.cs // CONTRIBUTOR: Jeff Lill // COPYRIGHT: Copyright (c) 2021 by neonFORGE, LLC. All rights reserved. @@ -108,6 +108,8 @@ private async void Execute(object sender, EventArgs e) { await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); + Log.Clear(); + if (!await DebugHelper.EnsureOpenSshAsync()) { return; @@ -172,40 +174,58 @@ private async void Execute(object sender, EventArgs e) var launchReady = false; var foundWebServer = WebServer.None; - await NeonHelper.WaitForAsync(async () => - { - // The developer must have stopped debugging before the - // ASPNET application was able to begin servicing requests. - if (dte.Mode != vsIDEMode.vsIDEModeDebug) return true; - - using (new CursorWait()) + try + { + await NeonHelper.WaitForAsync( async () => { - try - { - var (found, webServer) = - await SearchForRunningWebServerAsync(projectProperties, projectSettings, connection); - - // web server not found - if (!found) return false; + await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); - // take the found web server - foundWebServer = webServer; - launchReady = true; + // The developer must have stopped debugging before the + // ASPNET application was able to begin servicing requests. + if ( dte.Mode != vsIDEMode.vsIDEModeDebug ) return true; - return true; - } - catch + using ( new CursorWait() ) { - return false; + try + { + var (found, webServer) = + await SearchForRunningWebServerAsync( projectProperties, projectSettings, + connection ); + + // web server not found + if ( !found ) return false; + + // take the found web server + foundWebServer = webServer; + launchReady = true; + + return true; + } + catch + { + return false; + } } - } - }, - timeout: TimeSpan.FromSeconds(60), - pollInterval: TimeSpan.FromSeconds(0.5)); + }, + timeout: TimeSpan.FromSeconds( 60 ), + pollInterval: TimeSpan.FromSeconds( 0.5 ) ); + } + catch ( TimeoutException ) + { + //DialogResult result = Dialogs.MessageBoxEx.Show( + // "Raspberry Web Server not found. Start web browser anyway?", + // "Raspberry Web Server", + // MessageBoxButtons.YesNo, + // MessageBoxIcon.Exclamation, + // MessageBoxDefaultButton.Button1); + + //launchReady = result == DialogResult.Ignore; + } if (!launchReady) return; - OpenWebBrowser(projectProperties, foundWebServer, connection); + await Task.Delay(3000); + OpenWebBrowser(projectProperties, projectSettings, foundWebServer, connection); } } @@ -213,16 +233,17 @@ await NeonHelper.WaitForAsync(async () => /// Open web browser for debugging /// /// Related project properties + /// /// Active WebServer: Kestrel or Other (NGiNX, Apache, etc.) /// LinuxSshProxy connection - private static void OpenWebBrowser( - ProjectProperties projectProperties, - WebServer foundWebServer, + private static void OpenWebBrowser(ProjectProperties projectProperties, + ProjectSettings projectSettings, + WebServer foundWebServer, LinuxSshProxy connection) { // only '/' present or full relative uri const int fullRelativeUri = 2; - var baseUri = $"http://{connection.Name}.local"; + var baseUri = projectSettings.UseWebServerProxy ? $"http://{connection.Name}.local" : $"http://{connection.Name}"; var relativeBrowserUri = projectProperties.AspRelativeBrowserUri.FirstOrDefault() == '/' ? projectProperties.AspRelativeBrowserUri diff --git a/RaspberryDebugger/Commands/ProxyWebServer.cs b/RaspberryDebugger/Commands/ProxyWebServer.cs index 155c032..23c81ee 100644 --- a/RaspberryDebugger/Commands/ProxyWebServer.cs +++ b/RaspberryDebugger/Commands/ProxyWebServer.cs @@ -37,13 +37,13 @@ private static (bool, WebServer) SearchKrestel(int aspPort, LinuxSshProxy connec { // search for dotnet kestrel web server var appKestrelListeningScript = - $@" - if lsof -i -P -n | grep --quiet 'dotnet\|TCP\|:{aspPort}' ; then - exit 0 - else - exit 1 - fi - "; + $""" + if lsof -i -P -n | grep --quiet 'dotnet\|TCP\|:{aspPort}' ; then + exit 0 + else + exit 1 + fi + """; var response = ExecSudoCmd(appKestrelListeningScript, connection); @@ -62,13 +62,13 @@ private static (bool, WebServer) SearchReverseProxy(int aspPort, LinuxSshProxy c { // search for web server running as reverse proxy var appWebServerListeningScript = - $@" - if lsof -i -P -n | grep --quiet 'TCP 127.0.0.1:{aspPort}' ; then - exit 0 - else - exit 1 - fi - "; + $""" + if lsof -i -P -n | grep --quiet 'TCP 127.0.0.1:{aspPort}' ; then + exit 0 + else + exit 1 + fi + """; var response = ExecSudoCmd(appWebServerListeningScript, connection); diff --git a/RaspberryDebugger/Commands/SettingsCommand.cs b/RaspberryDebugger/Commands/SettingsCommand.cs index 1578214..494d0ca 100644 --- a/RaspberryDebugger/Commands/SettingsCommand.cs +++ b/RaspberryDebugger/Commands/SettingsCommand.cs @@ -1,4 +1,4 @@ -//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- // FILE: SettingsCommand.cs // CONTRIBUTOR: Jeff Lill // COPYRIGHT: Copyright (c) 2021 by neonFORGE, LLC. All rights reserved. @@ -69,7 +69,7 @@ private SettingsCommand(AsyncPackage package, OleMenuCommandService commandServi { var command = (OleMenuCommand)s; - command.Visible = PackageHelper.IsActiveProjectRaspberryCompatible(dte); + command.Visible = PackageHelper.IsActiveProjectRaspberryExecutable(dte); }; commandService?.AddCommand(menuItem); diff --git a/RaspberryDebugger/Connection/Connection.cs b/RaspberryDebugger/Connection/Connection.cs index aef2060..6bb7951 100644 --- a/RaspberryDebugger/Connection/Connection.cs +++ b/RaspberryDebugger/Connection/Connection.cs @@ -1,4 +1,4 @@ -//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- // FILE: Connection.cs // CONTRIBUTOR: Jeff Lill // COPYRIGHT: Copyright (c) 2021 by neonFORGE, LLC. All rights reserved. @@ -125,7 +125,7 @@ public static async Task ConnectAsync(ConnectionInfo connectionInfo, try { - var connection = await ConnectAsync(connectionInfo, usePassword: true); + var connection = await ConnectAsync(connectionInfo, usePassword: true, projectSettings); // Append the public key to the user's [authorized_keys] file if it's // not already present. @@ -135,17 +135,17 @@ public static async Task ConnectAsync(ConnectionInfo connectionInfo, var homeFolder = LinuxPath.Combine("/", "home", connectionInfo.User); var publicKey = File.ReadAllText(connectionInfo.PublicKeyPath).Trim(); var keyScript = - $@" - mkdir -p {homeFolder}/.ssh - touch {homeFolder}/.ssh/authorized_keys - - if ! grep --quiet '{publicKey}' {homeFolder}/.ssh/authorized_keys ; then - echo '{publicKey}' >> {homeFolder}/.ssh/authorized_keys - exit $? - fi - - exit 0 - "; + $""" + mkdir -p {homeFolder}/.ssh + touch {homeFolder}/.ssh/authorized_keys + + if ! grep --quiet '{publicKey}' {homeFolder}/.ssh/authorized_keys ; then + echo '{publicKey}' >> {homeFolder}/.ssh/authorized_keys + exit $? + fi + + exit 0 + """; connection.ThrowOnError(connection.RunCommand(CommandBundle.FromScript(keyScript))); return connection; @@ -162,7 +162,7 @@ exit 0 RaspberryDebugger.Log.Exception(e, $"[{connectionInfo?.Host}]"); Log($"[{connectionInfo?.Host}]: Cannot reach device."); - return null; + throw; } } @@ -309,84 +309,99 @@ await PackageHelper.ExecuteWithProgressAsync( // We're going to execute a script the gathers everything in a single operation for speed. Log($"[{Name}]: Retrieving status"); - var statusScript = - $@" - # This script will return the status information via STDOUT line-by-line - # in this order: - # - # Chip Architecture - # PATH environment variable - # Unzip Installed (""unzip"" or ""unzip-missing"") - # Debugger Installed (""debugger-installed"" or ""debugger-missing"") - # List of installed SDKs names (e.g. 3.1.108) separated by commas - # Raspberry Model like: Raspberry Pi 4 Model B Rev 1.2 - # Raspberry Revision like: c03112 - # - # This script also ensures that the [/lib/dotnet] directory exists, that - # it has reasonable permissions, and that the folder exists on the system - # PATH and that DOTNET_ROOT points to the folder. - - # Set the SDK and debugger installation paths. - - DOTNET_ROOT={PackageHelper.RemoteDotnetFolder} - DEBUGFOLDER={PackageHelper.RemoteDebuggerFolder} - - # Get the chip architecture - uname -m - - # Get the current PATH - echo $PATH - - # Detect whether [unzip] is installed. - if which unzip &> /dev/nul ; then - echo 'unzip' - else - echo 'unzip-missing' - fi + // VS tries to update/install the debugger on every Attach to Process command. + // This conversion to user path would go away if the extension always tried the install. + var userDebugFolder = PackageHelper.RemoteDebuggerFolder.Replace( "~", LinuxPath.Combine("/", "home", Username) ); - # Detect whether the [vsdbg] debugger is installed. - if [ -d $DEBUGFOLDER ] ; then - echo 'debugger-installed' - else - echo 'debugger-missing' - fi - - # List the SDK folders. These folder names are the same as the - # corresponding SDK name. We'll list the files on one line - # with the SDK names separated by commas. We'll return a blank - # line if the SDK directory doesn't exist. - if [ -d $DOTNET_ROOT/sdk ] ; then - ls -m $DOTNET_ROOT/sdk - else - echo '' - fi - - # Output the Raspberry board model. - cat /proc/cpuinfo | grep '^Model\s' | grep -o 'Raspberry.*$' - - # Output the Raspberry board revision. - cat /proc/cpuinfo | grep 'Revision\s' | grep -o '[0-9a-fA-F]*$' - - # Ensure that the [/lib/dotnet] folder exists, that it's on the - # PATH and that DOTNET_ROOT are defined. - mkdir -p /lib/dotnet - chown root:root /lib/dotnet - chmod 755 /lib/dotnet - - # Set these for the current session: - export DOTNET_ROOT={PackageHelper.RemoteDotnetFolder} - export PATH=$PATH:$DOTNET_ROOT - - # and for future sessions too: - if ! grep --quiet DOTNET_ROOT /etc/profile ; then - echo """" >> /etc/profile - echo ""#------------------------------"" >> /etc/profile - echo ""# Raspberry Debugger:"" >> /etc/profile - echo ""export DOTNET_ROOT=$DOTNET_ROOT"" >> /etc/profile - echo ""export PATH=$PATH"" >> /etc/profile - echo ""#------------------------------"" >> /etc/profile - fi - "; + var statusScript = + $""" + # This script will return the status information via STDOUT line-by-line + # in this order: + # + # Chip Architecture + # PATH environment variable + # Unzip Installed ("unzip" or "unzip-missing") + # Lsof Installed ("lsof" or "lsof-missing") + # Debugger Installed ("debugger-installed" or "debugger-missing") + # List of installed SDKs names (e.g. 3.1.108) separated by commas + # Raspberry Model like: Raspberry Pi 4 Model B Rev 1.2 + # Raspberry Revision like: c03112 + # + # This script also ensures that the [/lib/dotnet] directory exists, that + # it has reasonable permissions, and that the folder exists on the system + # PATH and that DOTNET_ROOT points to the folder. + + # Set the SDK and debugger installation paths. + + DOTNET_ROOT="{PackageHelper.RemoteDotnetFolder}" + DEBUGFOLDER="{userDebugFolder}" + + # Get the chip architecture + uname -m + + # Get the kernel bit size + getconf LONG_BIT + + # Get the current PATH + echo $PATH + + # Detect whether [unzip] is installed. + if which unzip &> /dev/null ; then + echo 'unzip' + else + echo 'unzip-missing' + fi + + # Detect whether [lsof] is installed. + if which lsof &> /dev/null ; then + echo 'lsof' + else + echo 'lsof-missing' + fi + + # Detect whether the [vsdbg] debugger is installed. + if [ -d "$DEBUGFOLDER" ] ; then + echo 'debugger-installed' + else + echo 'debugger-missing' + fi + + # List the SDK folders. These folder names are the same as the + # corresponding SDK name. We'll list the files on one line + # with the SDK names separated by commas. We'll return a blank + # line if the SDK directory doesn't exist. + if [ -d "$DOTNET_ROOT"/sdk ] ; then + ls -m "$DOTNET_ROOT"/sdk + else + echo '' + fi + + # Output the Raspberry board model. + cat /proc/cpuinfo | grep '^Model\s' | grep -o 'Raspberry.*$' + + # Output the Raspberry board revision. + cat /proc/cpuinfo | grep 'Revision\s' | grep -o '[0-9a-fA-F]*$' + + # Ensure that the [/lib/dotnet] folder exists, that it's on the + # PATH and that DOTNET_ROOT are defined. + mkdir -p /lib/dotnet + chown root:root /lib/dotnet + chmod 755 /lib/dotnet + + # Set these for the current session: + export DOTNET_ROOT={PackageHelper.RemoteDotnetFolder} + export PATH=$PATH:$DOTNET_ROOT + + # and for future sessions too: + if ! grep --quiet DOTNET_ROOT /etc/profile ; then + echo "" >> /etc/profile + echo "#------------------------------" >> /etc/profile + echo "# Raspberry Debugger:" >> /etc/profile + echo "export DOTNET_ROOT=$DOTNET_ROOT" >> /etc/profile + echo "export PATH=$PATH" >> /etc/profile + echo "#------------------------------" >> /etc/profile + fi + """; Log($"[{Name}]: Fetching status"); @@ -396,8 +411,10 @@ chmod 755 /lib/dotnet using (var reader = new StringReader(response.OutputText)) { var processor = await reader.ReadLineAsync(); + var kernelBit = await reader.ReadLineAsync(); var path = await reader.ReadLineAsync(); var hasUnzip = await reader.ReadLineAsync() == "unzip"; + var hasLsof = await reader.ReadLineAsync() == "lsof"; var hasDebugger = await reader.ReadLineAsync() == "debugger-installed"; var sdkLine = await reader.ReadLineAsync(); var model = await reader.ReadLineAsync(); @@ -406,8 +423,10 @@ chmod 755 /lib/dotnet revision = revision.Trim(); // Remove any whitespace at the end. Log($"[{Name}]: processor: {processor}"); + Log($"[{Name}]: kernelBit: {kernelBit}"); Log($"[{Name}]: path: {path}"); Log($"[{Name}]: unzip: {hasUnzip}"); + Log($"[{Name}]: lsof: {hasLsof}"); Log($"[{Name}]: debugger: {hasDebugger}"); Log($"[{Name}]: sdks: {sdkLine}"); Log($"[{Name}]: model: {model}"); @@ -422,33 +441,22 @@ chmod 755 /lib/dotnet if (OperatingSystem.Bitness64.Any(bitness => processor.Contains(bitness))) { - osBitness = SdkArchitecture.Arm64; + // A 64bit arm arch running a 32 bit OS. + // https://developercommunity.visualstudio.com/t/VS2022-remote-debugging-over-SSH-does-no/10394545 + osBitness = kernelBit == "32" ? SdkArchitecture.Arm32 : SdkArchitecture.Arm64; } // Convert the comma separated SDK names into a [PiSdk] list. - var sdks = new List(); - - foreach (var sdkName in sdkLine - .Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries) - .Select(sdk => sdk.Trim())) - { - var sdkCatalogItem = PackageHelper.SdkCatalog.Items - .SingleOrDefault(item => item.Link.Contains(sdkName) && item.Architecture == osBitness); - - if (sdkCatalogItem != null) - { - sdks.Add(new Sdk(sdkName, osBitness)); - } - else - { - LogWarning($".NET SDK [{sdkName}] is present on [{Name}] but is not known to the RaspberryDebugger extension. Consider updating the extension."); - } - } + var sdks = sdkLine + .Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries) + .Select(sdk => new Version(sdk.Trim())) + .ToList(); PiStatus = new Status( processor: processor, path: path, hasUnzip: hasUnzip, + hasLsof: hasLsof, hasDebugger: hasDebugger, installedSdks: sdks, model: model, @@ -480,19 +488,19 @@ await PackageHelper.ExecuteWithProgressAsync("Creating SSH keys...", try { var createKeyScript = - $@" - # Create the key pair - if ! ssh-keygen -t rsa -b 2048 -P '' -C '{workstationUser}@{workstationName}' -f {tempPrivateKeyPath} -m pem ; then - exit 1 - fi - - # Append the public key to the user's [authorized_keys] file to enable it. - mkdir -p {homeFolder}/.ssh - touch {homeFolder}/.ssh/authorized_keys - cat {tempPublicKeyPath} >> {homeFolder}/.ssh/authorized_keys - - exit 0 - "; + $""" + # Create the key pair + if ! ssh-keygen -t rsa -b 2048 -P '' -C '{workstationUser}@{workstationName}' -f {tempPrivateKeyPath} -m pem ; then + exit 1 + fi + + # Append the public key to the user's [authorized_keys] file to enable it. + mkdir -p {homeFolder}/.ssh + touch {homeFolder}/.ssh/authorized_keys + cat {tempPublicKeyPath} >> {homeFolder}/.ssh/authorized_keys + + exit 0 + """; ThrowOnError(RunCommand(CommandBundle.FromScript(createKeyScript))); @@ -521,10 +529,10 @@ exit 0 { // Delete the temporary key files on the Raspberry. var removeKeyScript = - $@" - rm -f {tempPrivateKeyPath} - rm -f {tempPublicKeyPath} - "; + $""" + rm -f {tempPrivateKeyPath} + rm -f {tempPublicKeyPath} + """; ThrowOnError(SudoCommand(CommandBundle.FromScript(removeKeyScript))); } @@ -538,14 +546,27 @@ exit 0 /// Download and install actual (latest) SDK /// /// true if successful - public async Task SetupSdkAsync() + public async Task SetupSdkAsync(Version projectSdkVersion, Status piStatus ) { - if (IsSdkPresent()) return true; + // .NET 3.1, 6 and 7 are supported. When 8 arrives this should work. + if (projectSdkVersion.Major != 3 && + projectSdkVersion.Major < 6 ) + { + // project requires something not available... + return false; + } - var targetSdk = ReadActualSdkCatalogItem(); + if ( this.PiStatus.InstalledSdks.Any( sdk => sdk.Major == projectSdkVersion.Major && + sdk.Minor == projectSdkVersion.Minor) ) + { + // project sdk is already installed. + return true; + } - return await DownloadSdkAsync(targetSdk) && - await InstallSdkAsync(targetSdk); + LogInfo($"The project requires .NET SDK {projectSdkVersion} which is not installed."); + + return await this.InstallSdkPrerequsitesAsync() && + await InstallSdkAsync(projectSdkVersion, piStatus); } /// @@ -553,56 +574,41 @@ public async Task SetupSdkAsync() /// The Raspberry architecture is driving the installation - the newest .NET Core version is taken /// /// true on success. - private async Task DownloadSdkAsync(SdkCatalogItem targetSdk) + private async Task InstallSdkPrerequsitesAsync() { - if (targetSdk == null) - { - LogError("RasberryDebug is unaware of .NET Core SDK."); - LogError("Try updating the RasberryDebug extension or report this issue at:"); - LogError("https://github.com/nforgeio/RaspberryDebugger/issues"); - - return await Task.FromResult(false); - } - else - { - LogInfo($".NET Core SDK [v{targetSdk.Release}] is not installed."); - } - // Install the SDK. - LogInfo($"Downlaoding SDK v{targetSdk.Release}"); - - var downloadSdkInfo = - $"Download SDK for .NET v{targetSdk.Release} " + - $"({targetSdk.Architecture.GetAttributeOfType().Value}) on Raspberry..."; + LogInfo($"Ensure that the packages required by .NET are installed"); - return await PackageHelper.ExecuteWithProgressAsync(downloadSdkInfo, + // https://learn.microsoft.com/en-us/dotnet/core/install/linux-ubuntu#dependencies + return await PackageHelper.ExecuteWithProgressAsync( + $"Setup prerequsites for .NET SDK on Raspberry {this.connectionInfo.Host}...", async () => { var downloadScript = - $@" - # Ensure that the packages required by .NET Core are installed: - # https://docs.microsoft.com/en-us/dotnet/core/install/linux-debian#dependencies - if ! apt-get update ; then - exit 1 - fi - - if ! apt-get install -yq libc6 libgcc1 libgssapi-krb5-2 libicu-dev libssl1.1 libstdc++6 zlib1g libgdiplus ; then - exit 1 - fi - - # Remove any existing SDK download. This might be - # present if a previous installation attempt failed. - if ! rm -f /tmp/dotnet-sdk.tar.gz ; then - exit 1 - fi - - # Download the SDK installation file to a temporary file. - if ! wget --quiet -O /tmp/dotnet-sdk.tar.gz {targetSdk.Link} ; then - exit 1 - fi - - exit 0 - "; + $""" + # Ensure that the packages required by .NET Core are installed: + # https://docs.microsoft.com/en-us/dotnet/core/install/linux-debian#dependencies + if ! apt-get update ; then + exit 1 + fi + + debianVersion=$(cat /etc/debian_version) + versionTwelve='12' + if dpkg --compare-versions $debianVersion gt $versionTwelve + then + # Debian 12 or newer + if ! apt-get install -yq libc6 libgcc-s1 libgssapi-krb5-2 libicu-dev libssl3 libstdc++6 zlib1g libgdiplus ; then + exit 1 + fi + else + # Older than Debian 12 + if ! apt-get install -yq libc6 libgcc1 libgssapi-krb5-2 libicu-dev libssl1.1 libstdc++6 zlib1g libgdiplus ; then + exit 1 + fi + fi + + exit 0 + """; try { @@ -610,6 +616,7 @@ exit 0 if (response.ExitCode == 0) { + LogInfo(response.AllText); return await Task.FromResult(true); } else @@ -626,64 +633,56 @@ exit 0 }); } + /// + /// Don't care about value, ToString result is important! + /// + enum SdkQuality + { + preview, + + // The final stable releases of the .NET SDK and Runtime. Intended for public use as well as production support. + GA + } + /// /// Installs the .NET Core SDK on the Raspberry if it's not already installed. /// The Raspberry architecture is driving the installation - the newest .NET Core version is taken /// /// true on success. - private async Task InstallSdkAsync(SdkCatalogItem targetSdk) + private async Task InstallSdkAsync(Version targetSdk, Status piStatus) { - // Install the SDK. - LogInfo($"Installing SDK v{targetSdk.Release}"); - - var installSdkInfo = - $"Install SDK for .NET v{targetSdk.Release} " + - $"({targetSdk.Architecture.GetAttributeOfType().Value}) on Raspberry..."; + var verbose = string.Empty;// "--verbose"; + var sdkQuality = SdkQuality.GA; - return await PackageHelper.ExecuteWithProgressAsync(installSdkInfo, + var arch = piStatus.Architecture == SdkArchitecture.Arm64 ? + "arm64" : + "arm"; + + // Install the SDK. + LogInfo($"Installing .NET SDK {targetSdk} {arch}"); + + // Use the install script to install the latest version of the .NET SDK rather than use the + // PackageHelper.SdkCatalog list which must be updated manually. It doesn't require looking + // up the link to the SDK install package. + // TODO? Should this be run every time to update? + // TODO? Use a JSON file to control updates + // see: https://learn.microsoft.com/en-us/dotnet/core/tools/dotnet-install-script + var installCommand = + $"""curl -sSL https://dot.net/v1/dotnet-install.sh | bash /dev/stdin --architecture {arch} --channel {targetSdk.ToString(2)} --quality {sdkQuality} --install-dir {PackageHelper.RemoteDotnetFolder} {verbose}"""; + + return await PackageHelper.ExecuteWithProgressAsync( + $"Installing .NET SDK {targetSdk} {arch} on Raspberry {this.connectionInfo.Host}...", async () => { - var installScript = - $@" - export DOTNET_ROOT={PackageHelper.RemoteDotnetFolder} - - # Verify the SHA512. - orgDir=$cwd - cd /tmp - - if ! echo '{targetSdk.Sha512} dotnet-sdk.tar.gz' | sha512sum --check - ; then - cd $orgDir - exit 1 - fi - - cd $orgDir - - # Make sure the installation directory exists. - if ! mkdir -p $DOTNET_ROOT ; then - exit 1 - fi - - # Unpack the SDK to the installation directory. - if ! tar -zxf /tmp/dotnet-sdk.tar.gz -C $DOTNET_ROOT --no-same-owner ; then - exit 1 - fi - - # Remove the temporary installation file. - if ! rm /tmp/dotnet-sdk.tar.gz ; then - exit 1 - fi - - exit 0 - "; - try { - var response = SudoCommand(CommandBundle.FromScript(installScript)); + var response = SudoCommand(installCommand); if (response.ExitCode == 0) { // Add the newly installed SDK to the list of installed SDKs. - PiStatus.InstalledSdks.Add(new Sdk(targetSdk.Name, targetSdk.Architecture)); + //PiStatus.InstalledSdks.Add(targetSdk); + LogInfo(response.AllText); return await Task.FromResult(true); } else @@ -700,39 +699,6 @@ exit 0 }); } - /// - /// Is any .NET Core SDK installed on the Raspberry Pi. - /// The Raspberry architecture is driving the installation - the newest .NET Core version is taken - /// - /// true on success. - private bool IsSdkPresent() - { - var sdkOnPi = PiStatus.InstalledSdks - .OrderByDescending(name => name.Name) - .FirstOrDefault(); - - var sdkOnPiVersion = sdkOnPi?.Name ?? string.Empty; - var sdkOnPiArchitecture = sdkOnPi?.Architecture ?? PiStatus.Architecture; - - return PiStatus.InstalledSdks.Any(sdk => sdk.Name == sdkOnPiVersion && - sdk.Architecture == sdkOnPiArchitecture); - } - - /// - /// Read actual (latest) SDK from catalog - /// - /// Latest target SDK - private SdkCatalogItem ReadActualSdkCatalogItem() - { - // Locate the standalone SDK for the request .NET version. - // Figure out the latest SDK version - Microsoft versioning: the highest number - var targetSdk = PackageHelper.SdkCatalog.Items - .OrderByDescending(item => item.Name) - .FirstOrDefault(item => item.Architecture == PiStatus.Architecture); - - return targetSdk; - } - /// /// Installs the vsdbg debugger on the Raspberry if it's not already installed. /// @@ -749,18 +715,24 @@ public async Task SetupDebuggerAsync() return await PackageHelper.ExecuteWithProgressAsync("Installing [vsdbg] debugger...", async () => { - var installScript = - $@" - if ! curl -sSL https://aka.ms/getvsdbgsh | /bin/sh /dev/stdin -v latest -l {PackageHelper.RemoteDebuggerFolder} ; then - exit 1 - fi - - exit 0 - "; + // Use VS paradigm for placing the debugger in the users home dir. + // see: https://developercommunity.visualstudio.com/t/VS2022-remote-debugging-over-SSH-does-no/10394545#T-N10410651 + // This structure is used when Debug->Attach to Process is used. Unfortunately it's tied to + // the VS version. + // The getvsdbgsh docs suggest that one should use the VS Version to select the proper debugger. I tried + // that as VS2026 has been released. Unfortunately, getvsdbgsh returns an error using "vs2026" as a -v option. + // At least it does at this time anyway. In the process of trying to reverse engineer the valid options for + // -v I noticed that for any version I submitted - vs2019/vs2022/vs2017/latest, the script always attempted + // to download the latest revision of vsdbg. So I'm just going to use latest until that decides to stop working. + // It's going to be installed in the dir associated with the VS version though, keeping the paradigm suggested + // above. + var installCommand = + //$"""curl -sSL https://aka.ms/getvsdbgsh | bash /dev/stdin -v {PackageHelper.VisualStudioVersion} -l {PackageHelper.RemoteDebuggerFolder}"""; + $"""curl -sSL https://aka.ms/getvsdbgsh | bash /dev/stdin -v latest -l {PackageHelper.RemoteDebuggerFolder}"""; try { - var response = SudoCommand(CommandBundle.FromScript(installScript)); + var response = SudoCommandAsUser(Username, installCommand); if (response.ExitCode == 0) { @@ -783,6 +755,52 @@ exit 0 }); } + /// + /// Installs the Linux dependencies on the Raspberry if it's not already installed. + /// + /// true on success. + public async Task SetupLinuxDependenciesAsync() + { + if (PiStatus.HasUnzip && PiStatus.HasLsof) + { + return await Task.FromResult(true); + } + + LogInfo("Installing Linux dependencies"); + + return await PackageHelper.ExecuteWithProgressAsync("Installing Linux dependencies...", + async () => + { + var installScript = + $""" + if ! apt-get install -yq {(PiStatus.HasLsof ? null : "lsof")} {(PiStatus.HasUnzip ? null : "unzip")} ; then + exit 1 + fi + + exit 0 + """; + + try + { + var response = SudoCommand(CommandBundle.FromScript(installScript)); + + if (response.ExitCode == 0) + { + PiStatus.HasUnzip = true; + PiStatus.HasLsof = true; + return await Task.FromResult(true); + } + LogError(response.AllText); + return await Task.FromResult(false); + } + catch (Exception e) + { + LogException(e); + return await Task.FromResult(false); + } + }); + } + /// /// Uploads the files for the program being debugged to the Raspberry, replacing /// any existing files. @@ -802,24 +820,27 @@ public async Task UploadProgramAsync(string programName, string assemblyNa // We're going to ZIP the program files locally and then transfer the zipped // files to the Raspberry to be expanded there. + return await PackageHelper.ExecuteWithProgressAsync( + $"Uploading program {programName}...", + async () => + { + var debugFolder = LinuxPath.Combine(PackageHelper.RemoteDebugBinaryRoot(Username), programName); + var groupScript = string.Empty; - var debugFolder = LinuxPath.Combine(PackageHelper.RemoteDebugBinaryRoot(Username), programName); - var groupScript = string.Empty; - - if (!string.IsNullOrEmpty(projectSettings.TargetGroup)) - { - groupScript = - $@" + if (!string.IsNullOrEmpty(projectSettings.TargetGroup)) + { + groupScript = + $@" # Add the program assembly to the user specified target group (if any). # This defaults to [gpio] so users will be able to access the GPIO pins. if ! chgrp {projectSettings.TargetGroup} {debugFolder}/{assemblyName} ; then exit 1 fi "; - } + } - var uploadScript = - $@" + var uploadScript = + $@" # Ensure that the debug folder exists. if ! mkdir -p {debugFolder} ; then exit 1 @@ -843,33 +864,35 @@ exit 1 exit 0 "; - // I'm not going to do a progress dialog because this should be fast. - try - { - LogInfo($"Uploading program to: [{debugFolder}]"); + // I'm not going to do a progress dialog because this should be fast. + try + { + LogInfo($"Uploading program to: [{debugFolder}]"); - var bundle = new CommandBundle(uploadScript); + var bundle = new CommandBundle(uploadScript); - bundle.AddZip("program.zip", publishedBinaryFolder); + bundle.AddZip("program.zip", publishedBinaryFolder); - var response = RunCommand(bundle); + var response = RunCommand(bundle); - if (response.ExitCode == 0) - { - LogInfo("Program uploaded"); - return await Task.FromResult(true); - } - else - { - LogError(response.AllText); - return await Task.FromResult(false); - } - } - catch (Exception e) - { - LogException(e); - return await Task.FromResult(false); - } + if (response.ExitCode == 0) + { + LogInfo("Program uploaded"); + return await Task.FromResult(true); + } + else + { + LogError(response.AllText); + return await Task.FromResult(false); + } + } + catch (Exception e) + { + LogException(e); + return await Task.FromResult(false); + } + + }); } } } diff --git a/RaspberryDebugger/Connection/Sdk.cs b/RaspberryDebugger/Connection/Sdk.cs deleted file mode 100644 index fd2aba5..0000000 --- a/RaspberryDebugger/Connection/Sdk.cs +++ /dev/null @@ -1,51 +0,0 @@ -//----------------------------------------------------------------------------- -// FILE: Sdk.cs -// CONTRIBUTOR: Jeff Lill -// COPYRIGHT: Copyright (c) 2021 by neonFORGE, LLC. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -using System; -using System.Diagnostics.Contracts; -using RaspberryDebugger.Models.Sdk; - -namespace RaspberryDebugger.Connection -{ - /// - /// Holds information about a .NET Core SDK installed on a Raspberry Pi. - /// - internal class Sdk - { - /// - /// Constructor. - /// - /// The SDK name. - /// The SDK bitness architecture. - public Sdk(string name, SdkArchitecture architecture = SdkArchitecture.Arm32) - { - Covenant.Requires(!string.IsNullOrEmpty(name), nameof(name)); - - this.Name = name; - this.Architecture = architecture; - } - - /// - /// Returns the name of the SDK (like 3.1.402). - /// - public string Name { get; } - - /// - /// The raspberry pi architecture - /// - public SdkArchitecture Architecture { get; } - } -} diff --git a/RaspberryDebugger/Connection/Status.cs b/RaspberryDebugger/Connection/Status.cs index 4e8e834..0439bb7 100644 --- a/RaspberryDebugger/Connection/Status.cs +++ b/RaspberryDebugger/Connection/Status.cs @@ -33,6 +33,7 @@ internal class Status /// /// The chip architecture. /// Indicates whether unzip is installed. + /// Indicates whether lsof is installed. /// Indicates whether the debugger is installed. /// The installed .NET Core SDKs. /// The current value of the PATH environment variable. @@ -43,8 +44,9 @@ public Status( string processor, string path, bool hasUnzip, + bool hasLsof, bool hasDebugger, - IEnumerable installedSdks, + IEnumerable installedSdks, string model, string revision, SdkArchitecture architecture) @@ -56,6 +58,7 @@ public Status( this.Processor = processor; this.Path = path; this.HasUnzip = hasUnzip; + this.HasLsof = hasLsof; this.HasDebugger = hasDebugger; this.InstalledSdks = installedSdks?.ToList(); this.RaspberryModel = model; @@ -77,7 +80,12 @@ public Status( /// Returns true if unzip is installed on the Raspberry Pi. /// This is required and will be installed automatically. /// - private bool HasUnzip { get; set; } + public bool HasUnzip { get; set; } + /// + /// Returns true if lsof is installed on the Raspberry Pi. + /// This is required and will be installed automatically. + /// + public bool HasLsof { get; set; } /// /// Indicates whether the vsdbg debugger is installed. @@ -87,7 +95,7 @@ public Status( /// /// Returns information about the .NET Core SDKs installed. /// - public List InstalledSdks { get; } + public List InstalledSdks { get; } /// /// Returns the Raspberry board model. diff --git a/RaspberryDebugger/DebugHelper.cs b/RaspberryDebugger/DebugHelper.cs index c46cb3d..01a578f 100644 --- a/RaspberryDebugger/DebugHelper.cs +++ b/RaspberryDebugger/DebugHelper.cs @@ -1,595 +1,660 @@ -//----------------------------------------------------------------------------- -// FILE: DebugHelper.cs -// CONTRIBUTOR: Jeff Lill -// COPYRIGHT: Copyright (c) 2021 by neonFORGE, LLC. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// ReSharper disable StringLiteralTypo -using System; -using System.Collections.Generic; -using System.Diagnostics.Contracts; -using System.IO; -using System.Linq; -using System.Threading.Tasks; -using System.Windows.Forms; - -using Microsoft.VisualStudio.Shell; -using Microsoft.VisualStudio.Shell.Interop; -using EnvDTE; -using EnvDTE80; - -using Neon.Common; -using Neon.Windows; -using RaspberryDebugger.Connection; -using RaspberryDebugger.Dialogs; -using RaspberryDebugger.Models.Connection; -using RaspberryDebugger.Models.Project; -using RaspberryDebugger.Models.Raspberry; -using RaspberryDebugger.Models.VisualStudio; -using Task = System.Threading.Tasks.Task; - -namespace RaspberryDebugger -{ - /// - /// Remote debugger related utilities. - /// - internal static class DebugHelper - { - private const string SupportedVersions = ".NET Core 3.1 or .NET 5 + 6"; - /// - /// Ensures that the native Windows OpenSSH client is installed, prompting - /// the user to install it if necessary. - /// - /// true if OpenSSH is installed. - public static async Task EnsureOpenSshAsync() - { - await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); - - Log.Info("Checking for native Windows OpenSSH client"); - - var openSshPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Windows), "System32", "OpenSSH", "ssh.exe"); - - if (!File.Exists(openSshPath)) - { - Log.WriteLine("Raspberry debugging requires the native Windows OpenSSH client. See this:"); - Log.WriteLine("https://techcommunity.microsoft.com/t5/itops-talk-blog/installing-and-configuring-openssh-on-windows-server-2019/ba-p/309540"); - - var button = MessageBox.Show( - @"Raspberry debugging requires the Windows OpenSSH client. - Would you like to install this now (restart required)?", - @"Windows OpenSSH Client Required", - MessageBoxButtons.YesNo, - MessageBoxIcon.Question, - MessageBoxDefaultButton.Button2); - - if (button != DialogResult.Yes) - { - return false; - } - - // Install via Powershell: https://techcommunity.microsoft.com/t5/itops-talk-blog/installing-and-configuring-openssh-on-windows-server-2019/ba-p/309540 - - await PackageHelper.ExecuteWithProgressAsync("Installing OpenSSH Client", - async () => - { - using (var powershell = new PowerShell()) - { - Log.Info("Installing OpenSSH"); - Log.Info(powershell.Execute("Add-WindowsCapability -Online -Name OpenSSH.Client~~~~0.0.1.0")); - } - - await Task.CompletedTask; - }); - - MessageBox.Show( - @"Restart Windows to complete the OpenSSH Client installation.", - @"Restart Required", - MessageBoxButtons.OK); - - return false; - } - else - { - return true; - } - } - - /// - /// Attempts to locate the startup project to be debugged, ensuring that it's - /// eligable for Raspberry debugging. - /// - /// The IDE. - /// The target project or null if there isn't a startup project or it wasn't eligible. - public static Project GetTargetProject(DTE2 dte) - { - ThreadHelper.ThrowIfNotOnUIThread(); - - // Identify the current startup project (if any). - - if (dte.Solution == null) - { - MessageBox.Show( - @"Please open a Visual Studio solution.", - @"Solution Required", - MessageBoxButtons.OK, - MessageBoxIcon.Information); - - return null; - } - - var project = PackageHelper.GetStartupProject(dte.Solution); - - if (project == null) - { - MessageBox.Show( - @"Please select a startup project for your solution.", - @"Startup Project Required", - MessageBoxButtons.OK, - MessageBoxIcon.Information); - - return null; - } - - // We need to capture the relevant project properties while we're still - // on the UI thread so we'll have them on background threads. - - var projectProperties = ProjectProperties.CopyFrom(dte.Solution, project); - - if (!projectProperties.IsNetCore) - { - MessageBox.Show( - $@"Only {SupportedVersions} projects are supported for Raspberry debugging.", - @"Invalid Project Type", - MessageBoxButtons.OK, - MessageBoxIcon.Error); - - return null; - } - - if (!projectProperties.IsExecutable) - { - MessageBox.Show( - @"Only projects types that generate an executable program are supported for Raspberry debugging.", - @"Invalid Project Type", - MessageBoxButtons.OK, - MessageBoxIcon.Error); - - return null; - } - - if (string.IsNullOrEmpty(projectProperties.SdkVersion)) - { - MessageBox.Show( - @"The .NET Core SDK version could not be identified.", - @"Invalid Project Type", - MessageBoxButtons.OK, - MessageBoxIcon.Error); - - return null; - } - - var sdkVersion = Version.Parse(projectProperties.SdkVersion); - - if (!projectProperties.IsSupportedSdkVersion) - { - MessageBox.Show( - $@"The .NET Core SDK [{sdkVersion}] is not currently supported. Only .NET Core versions [v3.1] or later will ever be supported - Note that we currently support only official SDKs (not previews or release candidates) and we check for new .NET Core SDKs every week or two. - Submit an issue if you really need support for a new SDK ASAP: - https://github.com/nforgeio/RaspberryDebugger/issues", - @"SDK Not Supported", - MessageBoxButtons.OK, - MessageBoxIcon.Error); - - return null; - } - - if (!projectProperties.AssemblyName.Contains(' ')) return project; - - MessageBox.Show( - $@"Your assembly name [{projectProperties.AssemblyName}] includes a space. This isn't supported.", - @"Unsupported Assembly Name", - MessageBoxButtons.OK, - MessageBoxIcon.Error); - - return null; - - } - - /// - /// Builds and publishes a project locally to prepare it for being uploaded to the Raspberry. This method - /// will display an error message box to the user on failures - /// - /// - /// - /// - /// - /// - public static async Task PublishProjectWithUiAsync(DTE2 dte, Solution solution, Project project, ProjectProperties projectProperties) - { - Covenant.Requires(dte != null, nameof(dte)); - Covenant.Requires(solution != null, nameof(solution)); - Covenant.Requires(project != null, nameof(project)); - Covenant.Requires(projectProperties != null, nameof(projectProperties)); - - await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); - - if (!await PublishProjectAsync(dte, solution, project, projectProperties)) - { - MessageBox.Show( - @"[dotnet publish] failed for the project. - Look at the Output/Debug panel for more details.", - @"Publish Failed", - MessageBoxButtons.OK, - MessageBoxIcon.Error); - - return false; - } - else - { - return true; - } - } - - /// - /// Builds and publishes a project locally to prepare it for being uploaded to the Raspberry. This method - /// does not display error message box to the user on failures - /// - /// The DTE. - /// The solution. - /// The project. - /// The project properties. - /// true on success. - private static async Task PublishProjectAsync(DTE2 dte, Solution solution, Project project, ProjectProperties projectProperties) - { - Covenant.Requires(dte != null, nameof(dte)); - Covenant.Requires(solution != null, nameof(solution)); - Covenant.Requires(project != null, nameof(project)); - Covenant.Requires(projectProperties != null, nameof(projectProperties)); - - // Build the project within the context of VS to ensure that all changed - // files are saved and all dependencies are built first. Then we'll - // verify that there were no errors before proceeding. - - await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); - - // Ensure that the project is completely loaded by Visual Studio. I've seen - // random crashes when building or publishing projects when VS is still loading - // projects. - - var solutionService4 = (IVsSolution4)await RaspberryDebuggerPackage.Instance.GetServiceAsync(typeof(SVsSolution)); - - if (solutionService4 == null) - { - // ReSharper disable once ConditionIsAlwaysTrueOrFalse - Covenant.Assert(solutionService4 != null, $"Service [{nameof(SVsSolution)}] is not available."); - } - - // Build the project to ensure that there are no compile-time errors. - - Log.Info($"Building: {projectProperties?.FullPath}"); - - solution?.SolutionBuild.BuildProject(solution.SolutionBuild.ActiveConfiguration.Name, project?.UniqueName, WaitForBuildToFinish: true); - - var errorList = dte?.ToolWindows.ErrorList.ErrorItems; - - if (errorList?.Count > 0) - { - for (var i = 1; i <= errorList.Count; i++) - { - var error = errorList.Item(i); - Log.Error($"{error.FileName}({error.Line},{error.Column}: {error.Description})"); - } - - Log.Error($"Build failed: [{errorList.Count}] errors"); - Log.Error("See the Build/Output panel for more information"); - return false; - } - - Log.Info("Build succeeded"); - - // Publish the project so all required binaries and assets end up - // in the output folder. - // - // Note that we're taking care to only forward a few standard - // environment variables because Visual Studio seems to communicate - // with dotnet related processes with environment variables and - // these can cause conflicts when we invoke [dotnet] below to - // publish the project. - - Log.Info($"Publishing: {projectProperties?.FullPath}"); - - await Task.Yield(); - - const string allowedVariableNames = - @" - ALLUSERSPROFILE - APPDATA - architecture - architecture_bits - CommonProgramFiles - CommonProgramFiles(x86) - CommonProgramW6432 - COMPUTERNAME - ComSpec - DOTNETPATH - DOTNET_CLI_TELEMETRY_OPTOUT - DriverData - HOME - HOMEDRIVE - HOMEPATH - LOCALAPPDATA - NUMBER_OF_PROCESSORS - OS - Path - PATHEXT - POWERSHELL_DISTRIBUTION_CHANNEL - PROCESSOR_ARCHITECTURE - PROCESSOR_IDENTIFIER - PROCESSOR_LEVEL - PROCESSOR_REVISION - ProgramData - ProgramFiles - ProgramFiles(x86) - ProgramW6432 - PUBLIC - SystemDrive - SystemRoot - TEMP - USERDOMAIN - USERDOMAIN_ROAMINGPROFILE - USERNAME - USERPROFILE - windir - "; - - var allowedVariables = new HashSet(StringComparer.InvariantCultureIgnoreCase); - var environmentVariables = new Dictionary(); - - using (var reader = new StringReader(allowedVariableNames)) - { - foreach (var line in reader.Lines()) - { - if (string.IsNullOrWhiteSpace(line)) - { - continue; - } - - allowedVariables.Add(line.Trim()); - } - } - - foreach (string variable in Environment.GetEnvironmentVariables().Keys) - { - if (allowedVariables.Contains(variable)) - { - environmentVariables[variable] = Environment.GetEnvironmentVariable(variable); - } - } - - try - { - ExecuteResponse response; - - if (!string.IsNullOrEmpty(projectProperties?.Framework)) - { - response = await NeonHelper.ExecuteCaptureAsync( - "dotnet", - new object[] - { - "publish", - "--configuration", projectProperties.Configuration, - "--framework", projectProperties.Framework, - "--runtime", projectProperties.Runtime, - "--no-self-contained", - "--output", projectProperties.PublishFolder, - projectProperties.FullPath - }, - environmentVariables: environmentVariables).ConfigureAwait(false); - } - else - { - response = await NeonHelper.ExecuteCaptureAsync( - "dotnet", - new object[] - { - "publish", - "--configuration", projectProperties?.Configuration, - "--runtime", projectProperties?.Runtime, - "--no-self-contained", - "--output", projectProperties?.PublishFolder, - projectProperties?.FullPath - }, - environmentVariables: environmentVariables).ConfigureAwait(false); - } - - await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); - - if (response.ExitCode == 0) - { - Log.Info("Publish succeeded"); - return true; - } - - Log.Error($"Publish failed: ExitCode={response.ExitCode}"); - Log.WriteLine(response.AllText); - - return false; - } - catch (Exception e) - { - Log.Error(NeonHelper.ExceptionError(e)); - - return false; - } - } - - /// - /// Maps the debug connection name we got from the project properties (if any) to - /// one of our Raspberry connections. If no name is specified, we'll - /// use the default connection or prompt the user to create a connection. - /// We'll display an error if a connection is specified and but doesn't exist. - /// - /// The project properties. - /// The connection or null when one couldn't be located. - public static ConnectionInfo GetDebugConnectionInfo(ProjectProperties projectProperties) - { - Covenant.Requires(projectProperties != null, nameof(projectProperties)); - var existingConnections = PackageHelper.ReadConnections(); - ConnectionInfo connectionInfo; - - if (string.IsNullOrEmpty(projectProperties?.DebugConnectionName)) - { - connectionInfo = existingConnections.SingleOrDefault(info => info.IsDefault); - - // ReSharper disable once InvertIf - if (connectionInfo == null) - { - if (MessageBoxEx.Show( - "Raspberry connection information required. Would you like to create a connection now?", - "Raspberry Connection Required", - MessageBoxButtons.YesNo, - MessageBoxIcon.Error, - MessageBoxDefaultButton.Button1) == DialogResult.No) - { - return null; - } - - connectionInfo = new ConnectionInfo(); - - var connectionDialog = new ConnectionDialog(connectionInfo, edit: false, existingConnections: existingConnections); - - if (connectionDialog.ShowDialog() == DialogResult.OK) - { - existingConnections.Add(connectionInfo); - PackageHelper.WriteConnections(existingConnections, disableLogging: true); - } - else - { - return null; - } - } - } - else - { - connectionInfo = existingConnections.SingleOrDefault(info => info.Name.Equals(projectProperties.DebugConnectionName, StringComparison.InvariantCultureIgnoreCase)); - - if (connectionInfo == null) - { - MessageBoxEx.Show( - $"The [{projectProperties.DebugConnectionName}] Raspberry connection does not exist.\r\n\r\nPlease add the connection via:\r\n\r\nTools/Options/Raspberry Debugger", - "Cannot Find Raspberry Connection", - MessageBoxButtons.OK, - MessageBoxIcon.Error); - - return null; - } - } - - return connectionInfo; - } - - /// - /// Establishes a connection to the Raspberry and ensures that the Raspberry has - /// the target SDK, vsdbg installed and also handles uploading of the project - /// binaries. - /// - /// The connection info. - /// The project properties. - /// The project's Raspberry debug settings. - /// The or null if there was an error. - public static async Task InitializeConnectionAsync(ConnectionInfo connectionInfo, ProjectProperties projectProperties, ProjectSettings projectSettings) - { - Covenant.Requires(connectionInfo != null, nameof(connectionInfo)); - Covenant.Requires(projectProperties != null, nameof(projectProperties)); - Covenant.Requires(projectSettings != null, nameof(projectSettings)); - - var connection = await Connection.Connection.ConnectAsync(connectionInfo, projectSettings: projectSettings); - - // device not found - if(connection == null) return null; - - var raspberryModel = new RaspberryModelCheck(); - - // .NET Core only supports Raspberry models 3 and 4. - if(raspberryModel.IsNotSupported(connection.PiStatus.RaspberryModel)) - { - await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); - - MessageBoxEx.Show( - $"Your [{raspberryModel.ActualType}] is not supported." + - $" This .NET version requires a {string.Join(" or ", raspberryModel.Supported)}.", - "Raspberry Not Supported", - MessageBoxButtons.OK, - MessageBoxIcon.Error); - - connection.Dispose(); - - return null; - } - - // Ensure that the SDK is installed. - if (!await connection.SetupSdkAsync()) - { - await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); - - MessageBoxEx.Show( - $"Cannot install the .NET SDK [v{connection.PiStatus}] on the Raspberry.\r\n\r\nCheck the Debug Output for more details.", - "SDK Installation Failed", - MessageBoxButtons.OK, - MessageBoxIcon.Error); - - connection.Dispose(); - - return null; - } - - // Ensure that the debugger is installed. - if (!await connection.SetupDebuggerAsync()) - { - await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); - - MessageBoxEx.Show( - "Cannot install the VSDBG debugger on the Raspberry.\r\n\r\nCheck the Debug Output for more details.", - "Debugger Installation Failed", - MessageBoxButtons.OK, - MessageBoxIcon.Error); - - connection.Dispose(); - - return null; - } - - // Upload the program binaries. - if (await connection.UploadProgramAsync( - projectProperties?.Name, - projectProperties?.AssemblyName, - projectProperties?.PublishFolder)) - - return connection; - - await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); - - MessageBoxEx.Show( - "Cannot upload the program binaries to the Raspberry.\r\n\r\nCheck the Debug Output for more details.", - "Debugger Installation Failed", - MessageBoxButtons.OK, - MessageBoxIcon.Error); - - connection.Dispose(); - - return null; - } - } -} - +//----------------------------------------------------------------------------- +// FILE: DebugHelper.cs +// CONTRIBUTOR: Jeff Lill +// COPYRIGHT: Copyright (c) 2021 by neonFORGE, LLC. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ReSharper disable StringLiteralTypo +using System; +using System.Collections.Generic; +using System.Diagnostics.Contracts; +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using System.Windows.Forms; + +using Microsoft.VisualStudio.Shell; +using Microsoft.VisualStudio.Shell.Interop; +using EnvDTE; +using EnvDTE80; + +using Neon.Common; +using Neon.Windows; +using RaspberryDebugger.Dialogs; +using RaspberryDebugger.Models.Connection; +using RaspberryDebugger.Models.Project; +using RaspberryDebugger.Models.Raspberry; +using RaspberryDebugger.Models.VisualStudio; +using Task = System.Threading.Tasks.Task; + +namespace RaspberryDebugger +{ + /// + /// Remote debugger related utilities. + /// + internal static class DebugHelper + { + private const string SupportedVersions = ".NET Core 3.1 or .NET 5 + 6"; + + /// + /// Track the last output file that was uploaded. + /// + private static DirectoryInfo LastUploadedPublishDirInfo { get; set; } + + /// + /// Ensures that the native Windows OpenSSH client is installed, prompting + /// the user to install it if necessary. + /// + /// true if OpenSSH is installed. + public static async Task EnsureOpenSshAsync() + { + await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); + + Log.Info("Checking for native Windows OpenSSH client"); + + var openSshPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Windows), "System32", "OpenSSH", "ssh.exe"); + + if (!File.Exists(openSshPath)) + { + Log.WriteLine("Raspberry debugging requires the native Windows OpenSSH client. See this:"); + Log.WriteLine("https://techcommunity.microsoft.com/t5/itops-talk-blog/installing-and-configuring-openssh-on-windows-server-2019/ba-p/309540"); + + var button = MessageBox.Show( + @"Raspberry debugging requires the Windows OpenSSH client. + Would you like to install this now (restart required)?", + @"Windows OpenSSH Client Required", + MessageBoxButtons.YesNo, + MessageBoxIcon.Question, + MessageBoxDefaultButton.Button2); + + if (button != DialogResult.Yes) + { + return false; + } + + // Install via Powershell: https://techcommunity.microsoft.com/t5/itops-talk-blog/installing-and-configuring-openssh-on-windows-server-2019/ba-p/309540 + + await PackageHelper.ExecuteWithProgressAsync("Installing OpenSSH Client", + async () => + { + using (var powershell = new PowerShell()) + { + Log.Info("Installing OpenSSH"); + Log.Info(powershell.Execute("Add-WindowsCapability -Online -Name OpenSSH.Client~~~~0.0.1.0")); + } + + await Task.CompletedTask; + }); + + MessageBox.Show( + @"Restart Windows to complete the OpenSSH Client installation.", + @"Restart Required", + MessageBoxButtons.OK); + + return false; + } + else + { + return true; + } + } + + /// + /// Attempts to locate the startup project to be debugged, ensuring that it's + /// eligable for Raspberry debugging. + /// + /// The IDE. + /// The target project or null if there isn't a startup project or it wasn't eligible. + public static Project GetTargetProject(DTE2 dte) + { + ThreadHelper.ThrowIfNotOnUIThread(); + + // Identify the current startup project (if any). + + if (dte.Solution == null) + { + MessageBox.Show( + @"Please open a Visual Studio solution.", + @"Solution Required", + MessageBoxButtons.OK, + MessageBoxIcon.Information); + + return null; + } + + var project = PackageHelper.GetStartupProject(dte.Solution); + + if (project == null) + { + MessageBox.Show( + @"Please select a startup project for your solution.", + @"Startup Project Required", + MessageBoxButtons.OK, + MessageBoxIcon.Information); + + return null; + } + + // We need to capture the relevant project properties while we're still + // on the UI thread so we'll have them on background threads. + + var projectProperties = ProjectProperties.CopyFrom(dte.Solution, project); + + if (!projectProperties.IsNetCore) + { + MessageBox.Show( + $@"Only {SupportedVersions} projects are supported for Raspberry debugging.", + @"Invalid Project Type", + MessageBoxButtons.OK, + MessageBoxIcon.Error); + + return null; + } + + if (!projectProperties.IsExecutable) + { + MessageBox.Show( + @"Only projects types that generate an executable program are supported for Raspberry debugging.", + @"Invalid Project Type", + MessageBoxButtons.OK, + MessageBoxIcon.Error); + + return null; + } + + if ( projectProperties.SdkVersion == null ) + { + MessageBox.Show( + @"The .NET Core SDK version could not be identified.", + @"Invalid Project Type", + MessageBoxButtons.OK, + MessageBoxIcon.Error); + + return null; + } + + if (!projectProperties.IsSupportedSdkVersion) + { + MessageBox.Show( + $@"The .NET Core SDK [{projectProperties.SdkVersion}] is not currently supported. Only .NET Core versions [v3.1] or later will ever be supported + Note that we currently support only official SDKs (not previews or release candidates) and we check for new .NET Core SDKs every week or two. + Submit an issue if you really need support for a new SDK ASAP: + https://github.com/nforgeio/RaspberryDebugger/issues", + @"SDK Not Supported", + MessageBoxButtons.OK, + MessageBoxIcon.Error); + + return null; + } + + if (!projectProperties.AssemblyName.Contains(' ')) return project; + + MessageBox.Show( + $@"Your assembly name [{projectProperties.AssemblyName}] includes a space. This isn't supported.", + @"Unsupported Assembly Name", + MessageBoxButtons.OK, + MessageBoxIcon.Error); + + return null; + + } + + /// + /// Builds and publishes a project locally to prepare it for being uploaded to the Raspberry. This method + /// will display an error message box to the user on failures + /// + /// + /// + /// + /// + /// + public static async Task PublishProjectWithUiAsync(DTE2 dte, Solution solution, Project project, ProjectProperties projectProperties) + { + Covenant.Requires(dte != null, nameof(dte)); + Covenant.Requires(solution != null, nameof(solution)); + Covenant.Requires(project != null, nameof(project)); + Covenant.Requires(projectProperties != null, nameof(projectProperties)); + + await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); + + if (!await BuildProjectAsync(dte, solution, project, projectProperties)) + { + // bring ErrorList window to the top of the z order. + dte.Application.ExecuteCommand("View.ErrorList", " "); + + await Task.Yield(); + + MessageBox.Show( + """ + There were one or more errors building the project. + Please review the Error List. + """, + @"Build Failed", + MessageBoxButtons.OK, + MessageBoxIcon.Error); + + return false; + } + + await Task.Yield(); + + if (!await PublishProjectAsync(dte, solution, project, projectProperties)) + { + // bring Output window to the top of the z order. + dte.Application.ExecuteCommand("View.Output", " "); + + await Task.Yield(); + + MessageBox.Show( + """ + There were one or more errors Publishing the project. + Please review the Output Window for more details. + """, + @"Publish Failed", + MessageBoxButtons.OK, + MessageBoxIcon.Error); + + return false; + } + + return true; + } + + /// + /// Builds and publishes a project locally to prepare it for being uploaded to the Raspberry. This method + /// does not display error message box to the user on failures + /// + /// The DTE. + /// The solution. + /// The project. + /// The project properties. + /// true on success. + private static async Task BuildProjectAsync(DTE2 dte, Solution solution, Project project, + ProjectProperties projectProperties) + { + // Build the project within the context of VS to ensure that all changed + // files are saved and all dependencies are built first. Then we'll + // verify that there were no errors before proceeding. + + await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); + + // Ensure that the project is completely loaded by Visual Studio. I've seen + // random crashes when building or publishing projects when VS is still loading + // projects. + + var solutionService4 = + (IVsSolution4)await RaspberryDebuggerPackage.Instance.GetServiceAsync(typeof(SVsSolution)); + + if (solutionService4 == null) + { + // ReSharper disable once ConditionIsAlwaysTrueOrFalse + Covenant.Assert(solutionService4 != null, $"Service [{nameof(SVsSolution)}] is not available."); + } + + // Build the project to ensure that there are no compile-time errors. + Log.Info($"Build Started: {project.FullName}, Configuration: {projectProperties.ConfigurationName}, Platform: {projectProperties.PlatformName}"); + + solution?.SolutionBuild.BuildProject( + solution.SolutionBuild.ActiveConfiguration.Name, project?.UniqueName, WaitForBuildToFinish: true); + + // if any projects failed to build + if (solution.SolutionBuild.LastBuildInfo != 0) + { + return false; + } + + Log.Info($"Build succeeded"); + + return true; + } + + /// + /// Builds and publishes a project locally to prepare it for being uploaded to the Raspberry. This method + /// does not display error message box to the user on failures + /// + /// The DTE. + /// The solution. + /// The project. + /// The project properties. + /// true on success. + private static async Task PublishProjectAsync(DTE2 dte, Solution solution, Project project, ProjectProperties projectProperties) + { + // Publish the project so all required binaries and assets end up + // in the output folder. + // + // Note that we're taking care to only forward a few standard + // environment variables because Visual Studio seems to communicate + // with dotnet related processes with environment variables and + // these can cause conflicts when we invoke [dotnet] below to + // publish the project. + + Log.Info($"Publishing Started: {project?.FullName}, RuntimeIdentifier: {projectProperties.RuntimeIdentifier}"); + + const string allowedVariableNames = + """ + ALLUSERSPROFILE + APPDATA + architecture + architecture_bits + CommonProgramFiles + CommonProgramFiles(x86) + CommonProgramW6432 + COMPUTERNAME + ComSpec + DOTNETPATH + DOTNET_CLI_TELEMETRY_OPTOUT + DriverData + HOME + HOMEDRIVE + HOMEPATH + LOCALAPPDATA + NUMBER_OF_PROCESSORS + OS + Path + PATHEXT + POWERSHELL_DISTRIBUTION_CHANNEL + PROCESSOR_ARCHITECTURE + PROCESSOR_IDENTIFIER + PROCESSOR_LEVEL + PROCESSOR_REVISION + ProgramData + ProgramFiles + ProgramFiles(x86) + ProgramW6432 + PUBLIC + SystemDrive + SystemRoot + TEMP + USERDOMAIN + USERDOMAIN_ROAMINGPROFILE + USERNAME + USERPROFILE + windir + """; + + var allowedVariables = new HashSet(StringComparer.InvariantCultureIgnoreCase); + var environmentVariables = new Dictionary(); + + using (var reader = new StringReader(allowedVariableNames)) + { + foreach (var line in reader.Lines()) + { + if (string.IsNullOrWhiteSpace(line)) + { + continue; + } + + allowedVariables.Add(line.Trim()); + } + } + + foreach (string variable in Environment.GetEnvironmentVariables().Keys) + { + if (allowedVariables.Contains(variable)) + { + environmentVariables[variable] = Environment.GetEnvironmentVariable(variable); + } + } + + ExecuteResponse response; + + try + { + var options = new List() + { + "publish", + "--configuration", projectProperties.ConfigurationName + }; + + if ( (bool)( projectProperties?.TargetFramework.HasValue ) ) + { + options.AddRange( + [ + "--framework", projectProperties.TargetFramework == DotNetFrameworks.DotNet_8 + ? "net8.0" + : "net9.0", // short term hack + ]); + } + + options.AddRange( [ + "--runtime", projectProperties.RuntimeIdentifier, + "--no-self-contained", + "--output", projectProperties.PublishFolder, + project?.FullName + ] ); + + Log.Info("dotnet Command:"); + Log.WriteLine( string.Join(" ", options.Select( p => p.ToString() ).ToArray())); + + response = await NeonHelper.ExecuteCaptureAsync( + "dotnet", + options.ToArray(), + environmentVariables: environmentVariables).ConfigureAwait(false); + + await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); + + if (response.ExitCode == 0) + { + Log.Info("Publish Succeeded"); + return true; + } + + Log.Error($"Publish failed: ExitCode={response.ExitCode}"); + Log.WriteLine(response.AllText); + } + catch (Exception e) + { + Log.Error($"Publish failed:"); + Log.Error(NeonHelper.ExceptionError(e)); + } + + return false; + } + + /// + /// Maps the debug connection name we got from the project properties (if any) to + /// one of our Raspberry connections. If no name is specified, we'll + /// use the default connection or prompt the user to create a connection. + /// We'll display an error if a connection is specified and but doesn't exist. + /// + /// The project properties. + /// The connection or null when one couldn't be located. + public static ConnectionInfo GetDebugConnectionInfo(ProjectProperties projectProperties) + { + Covenant.Requires(projectProperties != null, nameof(projectProperties)); + var existingConnections = PackageHelper.ReadConnections(); + ConnectionInfo connectionInfo; + + if (string.IsNullOrEmpty(projectProperties?.DebugConnectionName)) + { + connectionInfo = existingConnections.SingleOrDefault(info => info.IsDefault); + + // ReSharper disable once InvertIf + if (connectionInfo == null) + { + if (MessageBoxEx.Show( + "Raspberry connection information required. Would you like to create a connection now?", + "Raspberry Connection Required", + MessageBoxButtons.YesNo, + MessageBoxIcon.Error, + MessageBoxDefaultButton.Button1) == DialogResult.No) + { + return null; + } + + connectionInfo = new ConnectionInfo(); + + var connectionDialog = new ConnectionDialog(connectionInfo, edit: false, existingConnections: existingConnections); + + if (connectionDialog.ShowDialog() == DialogResult.OK) + { + existingConnections.Add(connectionInfo); + PackageHelper.WriteConnections(existingConnections, disableLogging: true); + } + else + { + return null; + } + } + } + else + { + connectionInfo = existingConnections.SingleOrDefault(info => info.Name.Equals(projectProperties.DebugConnectionName, StringComparison.InvariantCultureIgnoreCase)); + + if (connectionInfo == null) + { + MessageBoxEx.Show( + $"The [{projectProperties.DebugConnectionName}] Raspberry connection does not exist.\r\n\r\nPlease add the connection via:\r\n\r\nTools/Options/Raspberry Debugger", + "Cannot Find Raspberry Connection", + MessageBoxButtons.OK, + MessageBoxIcon.Error); + + return null; + } + } + + return connectionInfo; + } + + /// + /// Establishes a connection to the Raspberry and ensures that the Raspberry has + /// the target SDK, vsdbg installed and also handles uploading of the project + /// binaries. + /// + /// The connection info. + /// The project properties. + /// The project's Raspberry debug settings. + /// The or null if there was an error. + public static async Task InitializeConnectionAsync(ConnectionInfo connectionInfo, ProjectProperties projectProperties, ProjectSettings projectSettings) + { + Covenant.Requires(connectionInfo != null, nameof(connectionInfo)); + Covenant.Requires(projectProperties != null, nameof(projectProperties)); + Covenant.Requires(projectSettings != null, nameof(projectSettings)); + + var connection = await Connection.Connection.ConnectAsync(connectionInfo, projectSettings: projectSettings); + + // device not found + if(connection == null) return null; + + var raspberryModel = new RaspberryModelCheck(); + + // .NET Core only supports Raspberry models 3 and 4. + if(raspberryModel.IsNotSupported(connection.PiStatus.RaspberryModel)) + { + await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); + + MessageBoxEx.Show( + $"Your [{raspberryModel.ActualType}] is not supported." + + $" This .NET version requires a {string.Join(" or ", raspberryModel.Supported)}.", + "Raspberry Not Supported", + MessageBoxButtons.OK, + MessageBoxIcon.Error); + + connection.Dispose(); + + return null; + } + + // Ensure that the SDK is installed. + if (!await connection.SetupSdkAsync(projectProperties.SdkVersion, connection.PiStatus)) + { + await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); + + MessageBoxEx.Show( + $"Installation of the .NET SDK {projectProperties.SdkVersion} on the Raspberry was unsuccessful.\r\n\r\nCheck the Debug Output for more details.", + "SDK Installation Failed", + MessageBoxButtons.OK, + MessageBoxIcon.Error); + + connection.Dispose(); + + return null; + } + + // Ensure that the debugger is installed. + // TODO: Visual Studio tries to update/install the package each time it attaches. + // Maybe only try the update/install once per session/device? Lots of permutations to track tho. + // Would need to quietly fail if no internet connection. + if (!await connection.SetupDebuggerAsync()) + { + await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); + + MessageBoxEx.Show( + "Cannot install the VSDBG debugger on the Raspberry.\r\n\r\nCheck the Debug Output for more details.", + "Debugger Installation Failed", + MessageBoxButtons.OK, + MessageBoxIcon.Error); + + connection.Dispose(); + + return null; + } + + // Ensure that linux libraries are installed. + if (!await connection.SetupLinuxDependenciesAsync()) + { + await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); + + MessageBoxEx.Show( + "Cannot install the Linux dependencies on the Raspberry.\r\n\r\nCheck the Debug Output for more details.", + "Linux Dependencies Installation Failed", + MessageBoxButtons.OK, + MessageBoxIcon.Error); + + connection.Dispose(); + + return null; + } + + // look at the Build output and see if it's changed. + var dirInfo = new DirectoryInfo(projectProperties.PublishFolder); + + bool shouldUploadProgram = + LastUploadedPublishDirInfo == null || + LastUploadedPublishDirInfo.FullName != dirInfo.FullName || + LastUploadedPublishDirInfo.LastWriteTime != dirInfo.LastWriteTime; + + if (!shouldUploadProgram) + { + Log.Info($"Skipping upload of {projectProperties.PublishFolder}, {dirInfo.LastWriteTime}"); + + return connection; + } + + Log.Info($"Uploading {projectProperties.PublishFolder}, {dirInfo.LastWriteTime}"); + + // Upload the program binaries. + if (await connection.UploadProgramAsync( + projectProperties?.Name, + projectProperties?.AssemblyName, + projectProperties?.PublishFolder)) + { + LastUploadedPublishDirInfo = dirInfo; + + return connection; + } + + await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); + + MessageBoxEx.Show( + "Cannot upload the program binaries to the Raspberry.\r\n\r\nCheck the Debug Output for more details.", + "Debugger Installation Failed", + MessageBoxButtons.OK, + MessageBoxIcon.Error); + + connection.Dispose(); + + return null; + } + } +} diff --git a/RaspberryDebugger/Dialogs/ConnectionDialog.cs b/RaspberryDebugger/Dialogs/ConnectionDialog.cs index b7827be..b07d3e3 100644 --- a/RaspberryDebugger/Dialogs/ConnectionDialog.cs +++ b/RaspberryDebugger/Dialogs/ConnectionDialog.cs @@ -1,290 +1,290 @@ -//----------------------------------------------------------------------------- -// FILE: ConnectionDialog.cs -// CONTRIBUTOR: Jeff Lill -// COPYRIGHT: Copyright (c) 2021 by neonFORGE, LLC. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -using System; -using System.Collections.Generic; -using System.Linq; -using System.Net; -using System.Windows.Forms; -using Neon.Net; -using RaspberryDebugger.Models.Connection; - -namespace RaspberryDebugger.Dialogs -{ - /// - /// Implements the Add/Remove connection dialogs. - /// - internal partial class ConnectionDialog : Form - { - private const char PasswordChar = '•'; - - private readonly List existingConnections; - - /// - /// Constructor. - /// - /// The information for the connection being created or edited. - /// Pass true when editing, false for creating a new connection. - /// The existing connection. - public ConnectionDialog(ConnectionInfo connectionInfo, bool edit, List existingConnections) - { - InitializeComponent(); - - this.ConnectionInfo = connectionInfo; - this.Text = edit ? "Edit Raspberry Connection" : "New Raspberry Connection"; - this.existingConnections = existingConnections; - - // Initialize the controls on load. - this.Load += (s, a) => - { - hostTextBox.Text = connectionInfo.Host; - portTextBox.Text = connectionInfo.Port.ToString(); - userTextBox.Text = connectionInfo.User; - passwordTextBox.Text = connectionInfo.Password; - passwordTextBox.PasswordChar = PasswordChar; - showPasswordCheckBox.Checked = false; - instructionsTextBox.Visible = string.IsNullOrEmpty(connectionInfo.PrivateKeyPath); - }; - } - - public sealed override string Text - { - get => base.Text; - set => base.Text = value; - } - - /// - /// Returns the connection being created or edited. - /// - private ConnectionInfo ConnectionInfo { get; } - - /// - /// Handles the OK button. - /// - /// The sender. - /// The arguments. -#pragma warning disable VSTHRD100 - private async void okButton_Click(object sender, EventArgs args) -#pragma warning restore VSTHRD100 - { - //----------------------------------------------------------------- - // Validate the host - - var hostText = hostTextBox.Text.Trim(); - - if (hostText == string.Empty) - { - hostTextBox.Focus(); - hostTextBox.SelectAll(); - MessageBoxEx.Show(this, "You must specify a host name or IP address.", "Connection Error", MessageBoxButtons.OK); - return; - } - - if (!IPAddress.TryParse(hostText, out _) && !NetHelper.IsValidHost(hostText)) - { - portTextBox.Focus(); - portTextBox.SelectAll(); - MessageBoxEx.Show(this, $"[{hostText}] is not a valid IPv4 address or host name.", "Connection Error", MessageBoxButtons.OK); - return; - } - - //----------------------------------------------------------------- - // Validate the SSH port. - - var portText = portTextBox.Text.Trim(); - - if (portText == string.Empty) - { - portTextBox.Focus(); - portTextBox.SelectAll(); - MessageBoxEx.Show(this, "You must specify the SSH port.", "Connection Error", MessageBoxButtons.OK); - return; - } - - if (!int.TryParse(portText, out var port) || !NetHelper.IsValidPort(port)) - { - portTextBox.Focus(); - portTextBox.SelectAll(); - MessageBoxEx.Show(this, $"[{portText}] is not a valid SSH port.", "Connection Error", MessageBoxButtons.OK); - return; - } - - //----------------------------------------------------------------- - // Validate the username. - - var userText = userTextBox.Text.Trim(); - - if (userText == string.Empty) - { - userTextBox.Focus(); - userTextBox.SelectAll(); - MessageBoxEx.Show(this, "You must specify a username.", "Connection Error", MessageBoxButtons.OK); - return; - } - - var hasWhitespace = false; - var hasQuote = false; - - foreach (var ch in userText) - { - if (char.IsWhiteSpace(ch)) - { - hasWhitespace = true; - break; - } - else if (ch == '\'' || ch == '"') - { - hasQuote = true; - break; - } - } - - if (hasWhitespace) - { - userTextBox.Focus(); - userTextBox.SelectAll(); - MessageBoxEx.Show(this, "Username may not include whitespace.", "Connection Error", MessageBoxButtons.OK); - return; - } - - if (hasQuote) - { - userTextBox.Focus(); - userTextBox.SelectAll(); - MessageBoxEx.Show(this, "Username may not include single or double quotes.", "Connection Error", MessageBoxButtons.OK); - return; - } - - //----------------------------------------------------------------- - // Validate the password. - - var passwordText = passwordTextBox.Text.Trim(); - - if (passwordText == string.Empty && string.IsNullOrEmpty(ConnectionInfo.PrivateKeyPath)) - { - passwordTextBox.Focus(); - passwordTextBox.SelectAll(); - MessageBoxEx.Show(this, "You must specify a password until a SSH key has been created automatically for this connection.", "Connection Error", MessageBoxButtons.OK); - return; - } - - foreach (var ch in passwordText) - { - if (char.IsWhiteSpace(ch)) - { - hasWhitespace = true; - break; - } - else if (ch == '\'' || ch == '"') - { - hasQuote = true; - break; - } - } - - if (hasWhitespace) - { - passwordTextBox.Focus(); - passwordTextBox.SelectAll(); - MessageBoxEx.Show(this, "Password may not include whitespace.", "Connection Error", MessageBoxButtons.OK); - return; - } - - if (hasQuote) - { - passwordTextBox.Focus(); - passwordTextBox.SelectAll(); - MessageBoxEx.Show(this, "Password may not include single or double quotes.", "Connection Error", MessageBoxButtons.OK); - return; - } - - //----------------------------------------------------------------- - // Connection name needs to be unique. - - var connectionName = $"{userText}@{hostText}"; - - if (existingConnections.Any(connection => connection != this.ConnectionInfo && connection.Name == connectionName)) - { - portTextBox.Focus(); - portTextBox.SelectAll(); - MessageBoxEx.Show(this, $"Another connection already exists for [{connectionName}].", "Connection Error", MessageBoxButtons.OK); - return; - } - - //----------------------------------------------------------------- - // The properties look OK, so establish a connection to verify. - - var testConnectionInfo = new ConnectionInfo() - { - Host = hostText, - Port = port, - User = userText, - Password = passwordText, - PrivateKeyPath = ConnectionInfo.PrivateKeyPath, - PublicKeyPath = ConnectionInfo.PublicKeyPath - }; - - try - { - using (await Connection.Connection.ConnectAsync(testConnectionInfo)) { } - } - catch - { - MessageBoxEx.Show( - this, - $"Unable to connect to: {hostText}\r\n\r\nMake sure your Raspberry is turned on and verify your username and password.", - "Connection Failed", - MessageBoxButtons.OK, - MessageBoxIcon.Error); - - return; - } - - //----------------------------------------------------------------- - // Everything looks good, so update the connection and return. - - ConnectionInfo.Host = hostText; - ConnectionInfo.Port = port; - ConnectionInfo.User = userText; - ConnectionInfo.Password = passwordText; - ConnectionInfo.PrivateKeyPath = testConnectionInfo.PrivateKeyPath; - ConnectionInfo.PublicKeyPath = testConnectionInfo.PublicKeyPath; - - DialogResult = DialogResult.OK; - } - - /// - /// Handles the CANCEL button. - /// - /// The sender. - /// The arguments. - private void cancelButton_Click(object sender, EventArgs args) - { - DialogResult = DialogResult.Cancel; - } - - /// - /// Shows/hides the password. - /// - /// The sender. - /// The arguments. - private void showPasswordCheckBox_CheckedChanged(object sender, EventArgs args) - { - passwordTextBox.PasswordChar = showPasswordCheckBox.Checked ? (char)0 : PasswordChar; - } - } -} +//----------------------------------------------------------------------------- +// FILE: ConnectionDialog.cs +// CONTRIBUTOR: Jeff Lill +// COPYRIGHT: Copyright (c) 2021 by neonFORGE, LLC. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Windows.Forms; +using Neon.Net; +using RaspberryDebugger.Models.Connection; + +namespace RaspberryDebugger.Dialogs +{ + /// + /// Implements the Add/Remove connection dialogs. + /// + internal partial class ConnectionDialog : Form + { + private const char PasswordChar = '•'; + + private readonly List existingConnections; + + /// + /// Constructor. + /// + /// The information for the connection being created or edited. + /// Pass true when editing, false for creating a new connection. + /// The existing connection. + public ConnectionDialog(ConnectionInfo connectionInfo, bool edit, List existingConnections) + { + InitializeComponent(); + + this.ConnectionInfo = connectionInfo; + this.Text = edit ? "Edit Raspberry Connection" : "New Raspberry Connection"; + this.existingConnections = existingConnections; + + // Initialize the controls on load. + this.Load += (s, a) => + { + hostTextBox.Text = connectionInfo.Host; + portTextBox.Text = connectionInfo.Port.ToString(); + userTextBox.Text = connectionInfo.User; + passwordTextBox.Text = connectionInfo.Password; + passwordTextBox.PasswordChar = PasswordChar; + showPasswordCheckBox.Checked = false; + instructionsTextBox.Visible = string.IsNullOrEmpty(connectionInfo.PrivateKeyPath); + }; + } + + public sealed override string Text + { + get => base.Text; + set => base.Text = value; + } + + /// + /// Returns the connection being created or edited. + /// + private ConnectionInfo ConnectionInfo { get; } + + /// + /// Handles the OK button. + /// + /// The sender. + /// The arguments. +#pragma warning disable VSTHRD100 + private async void okButton_Click(object sender, EventArgs args) +#pragma warning restore VSTHRD100 + { + //----------------------------------------------------------------- + // Validate the host + + var hostText = hostTextBox.Text.Trim(); + + if (hostText == string.Empty) + { + hostTextBox.Focus(); + hostTextBox.SelectAll(); + MessageBoxEx.Show(this, "You must specify a host name or IP address.", "Connection Error", MessageBoxButtons.OK); + return; + } + + if (!IPAddress.TryParse(hostText, out _) && !NetHelper.IsValidDnsHost(hostText)) + { + portTextBox.Focus(); + portTextBox.SelectAll(); + MessageBoxEx.Show(this, $"[{hostText}] is not a valid IPv4 address or host name.", "Connection Error", MessageBoxButtons.OK); + return; + } + + //----------------------------------------------------------------- + // Validate the SSH port. + + var portText = portTextBox.Text.Trim(); + + if (portText == string.Empty) + { + portTextBox.Focus(); + portTextBox.SelectAll(); + MessageBoxEx.Show(this, "You must specify the SSH port.", "Connection Error", MessageBoxButtons.OK); + return; + } + + if (!int.TryParse(portText, out var port) || !NetHelper.IsValidPort(port)) + { + portTextBox.Focus(); + portTextBox.SelectAll(); + MessageBoxEx.Show(this, $"[{portText}] is not a valid SSH port.", "Connection Error", MessageBoxButtons.OK); + return; + } + + //----------------------------------------------------------------- + // Validate the username. + + var userText = userTextBox.Text.Trim(); + + if (userText == string.Empty) + { + userTextBox.Focus(); + userTextBox.SelectAll(); + MessageBoxEx.Show(this, "You must specify a username.", "Connection Error", MessageBoxButtons.OK); + return; + } + + var hasWhitespace = false; + var hasQuote = false; + + foreach (var ch in userText) + { + if (char.IsWhiteSpace(ch)) + { + hasWhitespace = true; + break; + } + else if (ch == '\'' || ch == '"') + { + hasQuote = true; + break; + } + } + + if (hasWhitespace) + { + userTextBox.Focus(); + userTextBox.SelectAll(); + MessageBoxEx.Show(this, "Username may not include whitespace.", "Connection Error", MessageBoxButtons.OK); + return; + } + + if (hasQuote) + { + userTextBox.Focus(); + userTextBox.SelectAll(); + MessageBoxEx.Show(this, "Username may not include single or double quotes.", "Connection Error", MessageBoxButtons.OK); + return; + } + + //----------------------------------------------------------------- + // Validate the password. + + var passwordText = passwordTextBox.Text.Trim(); + + if (passwordText == string.Empty && string.IsNullOrEmpty(ConnectionInfo.PrivateKeyPath)) + { + passwordTextBox.Focus(); + passwordTextBox.SelectAll(); + MessageBoxEx.Show(this, "You must specify a password until a SSH key has been created automatically for this connection.", "Connection Error", MessageBoxButtons.OK); + return; + } + + foreach (var ch in passwordText) + { + if (char.IsWhiteSpace(ch)) + { + hasWhitespace = true; + break; + } + else if (ch == '\'' || ch == '"') + { + hasQuote = true; + break; + } + } + + if (hasWhitespace) + { + passwordTextBox.Focus(); + passwordTextBox.SelectAll(); + MessageBoxEx.Show(this, "Password may not include whitespace.", "Connection Error", MessageBoxButtons.OK); + return; + } + + if (hasQuote) + { + passwordTextBox.Focus(); + passwordTextBox.SelectAll(); + MessageBoxEx.Show(this, "Password may not include single or double quotes.", "Connection Error", MessageBoxButtons.OK); + return; + } + + //----------------------------------------------------------------- + // Connection name needs to be unique. + + var connectionName = $"{userText}@{hostText}"; + + if (existingConnections.Any(connection => connection != this.ConnectionInfo && connection.Name == connectionName)) + { + portTextBox.Focus(); + portTextBox.SelectAll(); + MessageBoxEx.Show(this, $"Another connection already exists for [{connectionName}].", "Connection Error", MessageBoxButtons.OK); + return; + } + + //----------------------------------------------------------------- + // The properties look OK, so establish a connection to verify. + + var testConnectionInfo = new ConnectionInfo() + { + Host = hostText, + Port = port, + User = userText, + Password = passwordText, + PrivateKeyPath = ConnectionInfo.PrivateKeyPath, + PublicKeyPath = ConnectionInfo.PublicKeyPath + }; + + try + { + using (await Connection.Connection.ConnectAsync(testConnectionInfo)) { } + } + catch + { + MessageBoxEx.Show( + this, + $"Unable to connect to: {hostText}\r\n\r\nMake sure your Raspberry is turned on and verify your username and password.", + "Connection Failed", + MessageBoxButtons.OK, + MessageBoxIcon.Error); + + return; + } + + //----------------------------------------------------------------- + // Everything looks good, so update the connection and return. + + ConnectionInfo.Host = hostText; + ConnectionInfo.Port = port; + ConnectionInfo.User = userText; + ConnectionInfo.Password = passwordText; + ConnectionInfo.PrivateKeyPath = testConnectionInfo.PrivateKeyPath; + ConnectionInfo.PublicKeyPath = testConnectionInfo.PublicKeyPath; + + DialogResult = DialogResult.OK; + } + + /// + /// Handles the CANCEL button. + /// + /// The sender. + /// The arguments. + private void cancelButton_Click(object sender, EventArgs args) + { + DialogResult = DialogResult.Cancel; + } + + /// + /// Shows/hides the password. + /// + /// The sender. + /// The arguments. + private void showPasswordCheckBox_CheckedChanged(object sender, EventArgs args) + { + passwordTextBox.PasswordChar = showPasswordCheckBox.Checked ? (char)0 : PasswordChar; + } + } +} diff --git a/RaspberryDebugger/Log.cs b/RaspberryDebugger/Log.cs index 155853e..84f1999 100644 --- a/RaspberryDebugger/Log.cs +++ b/RaspberryDebugger/Log.cs @@ -16,7 +16,6 @@ using System.Runtime.Serialization.Formatters.Binary; using System.Text; using System.Threading.Tasks; - using Task = System.Threading.Tasks.Task; namespace RaspberryDebugger @@ -27,6 +26,14 @@ namespace RaspberryDebugger /// internal static class Log { + /// + /// Clear the debug pane + /// + public static void Clear() + { + RaspberryDebuggerPackage.LogClear(); + } + /// /// Writes text to the debug pane. /// @@ -42,7 +49,7 @@ public static void Write(string text) /// Optionally specifies the log text. public static void WriteLine(string text = "") { - RaspberryDebuggerPackage.Log(text + "\n"); + RaspberryDebuggerPackage.Log(text + Environment.NewLine); } /// @@ -51,7 +58,7 @@ public static void WriteLine(string text = "") /// The information text. public static void Info(string text) { - WriteLine($"[{PackageHelper.LogName}]: INFO: {text}"); + WriteLine($"INFO: {text}"); } /// @@ -60,7 +67,7 @@ public static void Info(string text) /// The error text. public static void Error(string text) { - WriteLine($"[{PackageHelper.LogName}]: ERROR: {text}"); + WriteLine($"ERROR: {text}"); } /// @@ -69,7 +76,7 @@ public static void Error(string text) /// The error text. public static void Warning(string text) { - WriteLine($"[{PackageHelper.LogName}]: WARNING: {text}"); + WriteLine($"[WARNING: {text}"); } /// @@ -87,21 +94,18 @@ public static void Exception(Exception e, string message = null) // We're going to build a multi-line message to reduce pressure // on the task/threading in [RaspberryDebugPackage.Log()]. - var sb = new StringBuilder(); - - sb.Append("\n"); + var sb = new StringBuilder().AppendLine(); if (string.IsNullOrEmpty(message)) { - sb.Append($"EXCEPTION: {e.GetType().FullName}: {e.Message}\n"); + sb.AppendLine($"EXCEPTION: {e.GetType().FullName}: {e.Message}"); } else { - sb.Append($"EXCEPTION: {e.GetType().FullName}: {message} {e.Message}\n"); + sb.AppendLine($"EXCEPTION: {e.GetType().FullName}: {message} {e.Message}"); } - sb.Append(e.StackTrace); - sb.Append("\n"); + sb.AppendLine(e.StackTrace); RaspberryDebuggerPackage.Log(sb.ToString()); } diff --git a/RaspberryDebugger/Models/Connection/ConnectionInfo.cs b/RaspberryDebugger/Models/Connection/ConnectionInfo.cs index 415b226..f326bb8 100644 --- a/RaspberryDebugger/Models/Connection/ConnectionInfo.cs +++ b/RaspberryDebugger/Models/Connection/ConnectionInfo.cs @@ -1,154 +1,174 @@ -//----------------------------------------------------------------------------- -// FILE: ConnectionInfo.cs -// CONTRIBUTOR: Jeff Lill -// COPYRIGHT: Copyright (c) 2021 by neonFORGE, LLC. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -using System.ComponentModel; -using System.Diagnostics.CodeAnalysis; -using Neon.Net; -using Newtonsoft.Json; -using RaspberryDebugger.OptionsPages; - -namespace RaspberryDebugger.Models.Connection -{ - /// - /// Describes a Raspberry Pi connection's network details and credentials. - /// - internal class ConnectionInfo - { - private bool isDefault; - private string host; - private string user = "pi"; - private string cachedName; - - /// - /// Returns the value to be used for sorting the connection. - /// - [JsonIgnore] - public string SortKey => Name.ToLowerInvariant(); - - /// - /// Returns the connection name like: user@host - /// - [JsonIgnore] - public string Name - { - get - { - if (cachedName != null) - { - return cachedName; - } - - return cachedName = $"{User}@{Host}"; - } - } - - /// - /// Indicates that this is the default connection. - /// - [JsonProperty(PropertyName = "IsDefault", Required = Required.Always)] - public bool IsDefault - { - get => isDefault; - - set - { - if (isDefault == value) return; - - isDefault = value; - ConnectionsPanel?.ConnectionIsDefaultChanged(this); - } - } - - /// - /// The Raspberry Pi host name or IP address. - /// - [JsonProperty(PropertyName = "Host", Required = Required.Always)] - public string Host - { - get => host; - - set - { - host = value; - cachedName = null; - } - } - - /// - /// The SSH port. - /// - [JsonProperty(PropertyName = "Port", Required = Required.Always)] - public int Port { get; set; } = NetworkPorts.SSH; - - /// - /// The user name. - /// - [JsonProperty(PropertyName = "User", Required = Required.Always)] - public string User - { - get => user; - - set - { - user = value; - cachedName = null; - } - } - - /// - /// The password. - /// - [JsonProperty(PropertyName = "Password", Required = Required.Default, DefaultValueHandling = DefaultValueHandling.IgnoreAndPopulate)] - [DefaultValue(null)] - public string Password { get; set; } = "raspberry"; - - /// - /// Path to the private key for this connection or null when one - /// hasn't been initialized yet. - /// - [JsonProperty(PropertyName = "PrivateKeyPath", Required = Required.Default, DefaultValueHandling = DefaultValueHandling.IgnoreAndPopulate)] - [DefaultValue(null)] - public string PrivateKeyPath { get; set; } - - /// - /// Path to the public key for this connection or null when one - /// hasn't been initialized yet. - /// - [JsonProperty(PropertyName = "PublicKeyPath", Required = Required.Default, DefaultValueHandling = DefaultValueHandling.IgnoreAndPopulate)] - [DefaultValue(null)] - public string PublicKeyPath { get; set; } - - /// - /// Describes the authentication method. - /// - [JsonIgnore] - public string Authentication => string.IsNullOrEmpty(PrivateKeyPath) ? "PASSWORD" : "SSH KEY"; - - /// - /// - /// This is a bit of a hack to call the - /// when the user changes the state of the property. The sender will be - /// the changed and the arguments will be empty. - /// - /// - /// This is a bit of hack because the control doesn't - /// appear to have a check box changed event. - /// - /// - [SuppressMessage("ReSharper", "InvalidXmlDocComment")] - internal ConnectionsPanel ConnectionsPanel { get; set; } - } -} +//----------------------------------------------------------------------------- +// FILE: ConnectionInfo.cs +// CONTRIBUTOR: Jeff Lill +// COPYRIGHT: Copyright (c) 2021 by neonFORGE, LLC. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +using Neon.Net; +using Newtonsoft.Json; +using RaspberryDebugger.Models.Sdk; +using RaspberryDebugger.OptionsPages; +using System.ComponentModel; +using System.Diagnostics.CodeAnalysis; + +namespace RaspberryDebugger.Models.Connection +{ + /// + /// Describes a Raspberry Pi connection's network details and credentials. + /// + internal class ConnectionInfo + { + private bool isDefault; + private string host; + private string user = "pi"; + private string cachedName; + private SdkArchitecture architecture = SdkArchitecture.Arm32; + + /// + /// Returns the value to be used for sorting the connection. + /// + [JsonIgnore] + public string SortKey => Name.ToLowerInvariant(); + + /// + /// Returns the connection name like: user@host + /// + [JsonIgnore] + public string Name + { + get + { + if (cachedName != null) + { + return cachedName; + } + + return cachedName = $"{User}@{Host}"; + } + } + + /// + /// Indicates that this is the default connection. + /// + [JsonProperty(PropertyName = "IsDefault", Required = Required.Always)] + public bool IsDefault + { + get => isDefault; + + set + { + if (isDefault == value) return; + + isDefault = value; + ConnectionsPanel?.ConnectionIsDefaultChanged(this); + } + } + + /// + /// The Raspberry Pi host name or IP address. + /// + [JsonProperty(PropertyName = "Host", Required = Required.Always)] + public string Host + { + get => host; + + set + { + host = value; + cachedName = null; + } + } + + /// + /// The SSH port. + /// + [JsonProperty(PropertyName = "Port", Required = Required.Always)] + public int Port { get; set; } = NetworkPorts.SSH; + + /// + /// Provides an architecture setting for this device so that build settings can be + /// pared correctly. + /// + [JsonProperty(PropertyName = "Architecture", Required = Required.Default)] + public SdkArchitecture Architecture + { + get => architecture; + + set + { + if (architecture == value) return; + + architecture = value; + ConnectionsPanel?.ConnectionIsDefaultChanged(this); + } + } + + /// + /// The user name. + /// + [JsonProperty(PropertyName = "User", Required = Required.Always)] + public string User + { + get => user; + + set + { + user = value; + cachedName = null; + } + } + + /// + /// The password. + /// + [JsonProperty(PropertyName = "Password", Required = Required.Default, DefaultValueHandling = DefaultValueHandling.IgnoreAndPopulate)] + [DefaultValue(null)] + public string Password { get; set; } = "raspberry"; + + /// + /// Path to the private key for this connection or null when one + /// hasn't been initialized yet. + /// + [JsonProperty(PropertyName = "PrivateKeyPath", Required = Required.Default, DefaultValueHandling = DefaultValueHandling.IgnoreAndPopulate)] + [DefaultValue(null)] + public string PrivateKeyPath { get; set; } + + /// + /// Path to the public key for this connection or null when one + /// hasn't been initialized yet. + /// + [JsonProperty(PropertyName = "PublicKeyPath", Required = Required.Default, DefaultValueHandling = DefaultValueHandling.IgnoreAndPopulate)] + [DefaultValue(null)] + public string PublicKeyPath { get; set; } + + /// + /// Describes the authentication method. + /// + [JsonIgnore] + public string Authentication => string.IsNullOrEmpty(PrivateKeyPath) ? "PASSWORD" : "SSH KEY"; + + /// + /// + /// This is a bit of a hack to call the + /// when the user changes the state of the property. The sender will be + /// the changed and the arguments will be empty. + /// + /// + /// This is a bit of hack because the control doesn't + /// appear to have a check box changed event. + /// + /// + [SuppressMessage("ReSharper", "InvalidXmlDocComment")] + internal ConnectionsPanel ConnectionsPanel { get; set; } + } +} diff --git a/RaspberryDebugger/Models/Raspberry/RaspberryModelCheck.cs b/RaspberryDebugger/Models/Raspberry/RaspberryModelCheck.cs index d836187..75e1f87 100644 --- a/RaspberryDebugger/Models/Raspberry/RaspberryModelCheck.cs +++ b/RaspberryDebugger/Models/Raspberry/RaspberryModelCheck.cs @@ -1,5 +1,6 @@ -using System.Linq; +using Org.BouncyCastle.Ocsp; using System.Collections.Generic; +using System.Linq; namespace RaspberryDebugger.Models.Raspberry { @@ -15,7 +16,9 @@ public RaspberryModelCheck() { "Raspberry Pi 3 Model", "Raspberry Pi 4 Model", + "Raspberry Pi 5 Model", "Raspberry Pi Compute Module 4", + "Raspberry Pi Compute Module 5", "Raspberry Pi Zero 2" }; } diff --git a/RaspberryDebugger/Models/Sdk/SdkCatalog.cs b/RaspberryDebugger/Models/Sdk/SdkCatalog.cs deleted file mode 100644 index a79d8a3..0000000 --- a/RaspberryDebugger/Models/Sdk/SdkCatalog.cs +++ /dev/null @@ -1,33 +0,0 @@ -//----------------------------------------------------------------------------- -// FILE: SdkCatalog.cs -// CONTRIBUTOR: Jeff Lill -// COPYRIGHT: Copyright (c) 2021 by neonFORGE, LLC. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -using System.Collections.Generic; -using Newtonsoft.Json; - -namespace RaspberryDebugger.Models.Sdk -{ - /// - /// Describes the available .NET Core SDK binary downloads for ARM. - /// - internal class SdkCatalog - { - /// - /// The list of SDK catalog items. - /// - [JsonProperty(PropertyName = "Items", Required = Required.Always)] - public List Items { get; set; } = new List(); - } -} diff --git a/RaspberryDebugger/Models/Sdk/SdkCatalogItem.cs b/RaspberryDebugger/Models/Sdk/SdkCatalogItem.cs deleted file mode 100644 index 9a305f6..0000000 --- a/RaspberryDebugger/Models/Sdk/SdkCatalogItem.cs +++ /dev/null @@ -1,73 +0,0 @@ -//----------------------------------------------------------------------------- -// FILE: SdkCatalogItem.cs -// CONTRIBUTOR: Jeff Lill -// COPYRIGHT: Copyright (c) 2021 by neonFORGE, LLC. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -using Newtonsoft.Json; - -namespace RaspberryDebugger.Models.Sdk -{ - /// - /// Describes an .NET Core SDK download. - /// - internal class SdkCatalogItem - { - /// - /// The SDK name (like "3.1.402"). - /// - [JsonProperty(PropertyName = "Name", Required = Required.Always)] - public string Name { get; set; } - - /// - /// The SDK Release (like "6.0.301"). - /// - [JsonProperty(PropertyName = "Release", Required = Required.Always)] - public string Release { get; set; } - - /// - /// Specifies the 32-bit or 64-bit version of the SDK. - /// - [JsonProperty(PropertyName = "Architecture", Required = Required.Always)] - public SdkArchitecture Architecture { get; set; } - - /// - /// The URL to the binary download. - /// - [JsonProperty(PropertyName = "Link", Required = Required.Always)] - public string Link { get; set; } - - /// - /// The SHA512 hash expected for the download.z - /// - [JsonProperty(PropertyName = "SHA512", Required = Required.Always)] - public string Sha512 { get; set; } - - /// - /// SdkCatalog Item Constructor - /// - /// SDK Name - /// SDK Release number - /// SDK type - /// Link for download - /// Checksum for download - public SdkCatalogItem(string name, string release, SdkArchitecture sdk, string link, string sha512) - { - Name = name; - Architecture = sdk; - Release = release; - Link = link; - Sha512 = sha512; - } - } -} diff --git a/RaspberryDebugger/Models/VisualStudio/ProjectProperties.cs b/RaspberryDebugger/Models/VisualStudio/ProjectProperties.cs index 13a3149..a9c2a12 100644 --- a/RaspberryDebugger/Models/VisualStudio/ProjectProperties.cs +++ b/RaspberryDebugger/Models/VisualStudio/ProjectProperties.cs @@ -30,6 +30,15 @@ namespace RaspberryDebugger.Models.VisualStudio { + /// + /// Determined through experimentation. + /// + enum DotNetFrameworks + { + DotNet_8 = 524288, + DotNet_9 = 589824, + } + /// /// The Visual Studio class properties can only be /// accessed from the UI thread, so we'll use this class to capture the @@ -63,63 +72,47 @@ public static ProjectProperties CopyFrom(Solution solution, EnvDTE.Project proje return new ProjectProperties() { - Name = project?.Name, - FullPath = project?.FullName, - Configuration = null, - IsNetCore = false, - SdkVersion = null, - OutputFolder = null, - OutputFileName = null, - IsExecutable = false, - AssemblyName = null, - DebugEnabled = false, - DebugConnectionName = null, - CommandLineArgs = new List(), - EnvironmentVariables = new Dictionary(), + Name = project?.Name, + FullPath = project?.FullName, + OutputPath = string.Empty, + ConfigurationName = null, + Guid = Guid.Empty, + IsNetCore = false, + SdkVersion = null, + OutputFileName = null, + IsExecutable = false, + RuntimeIdentifier = string.Empty, + AssemblyName = null, + DebugEnabled = false, + DebugConnectionName = null, + CommandLineArgs = new List(), + EnvironmentVariables = new Dictionary(), IsSupportedSdkVersion = false, IsRaspberryCompatible = false, - IsAspNet = false, - AspPort = 0, - AspLaunchBrowser = false, - AspRelativeBrowserUri = null + IsAspNet = false, + AspPort = 0, + AspLaunchBrowser = false, + AspRelativeBrowserUri = null, }; } - var projectFolder = Path.GetDirectoryName(project.FullName); + string fullPath = project.Properties.Item("FullPath").Value.ToString(); // Read the properties we care about from the project. - var targetFrameworkMonikers = (string)project.Properties.Item("TargetFrameworkMoniker").Value; + var targetFrameworkMonikers = (string)project.Properties.Item("TargetFrameworkMonikers").Value; + var targetFrameworkMoniker = (string)project.Properties.Item("TargetFrameworkMoniker").Value; var outputType = (int)project.Properties.Item("OutputType").Value; - var monikers = targetFrameworkMonikers.Split(','); + var targetFramework = (DotNetFrameworks)((int)project.Properties.Item("TargetFramework").Value); + + var moniker = targetFrameworkMoniker.Split(','); - var isNetCore = monikers[0] == ".NETCoreApp"; + var isNetCore = moniker[0] == ".NETCoreApp"; // Extract the version from the moniker. This looks like: "Version=v5.0" var versionRegex = new Regex(@"(?[0-9\.]+)$"); - var netVersion = SemanticVersion.Parse(versionRegex.Match(monikers[1]).Groups["version"].Value); - - var targetSdk = (RaspberryDebugger.Connection.Sdk)null; - var targetSdkVersion = (SemanticVersion)null; - - foreach (var sdkItem in PackageHelper.SdkCatalog.Items) - { - var sdkVersion = SemanticVersion.Parse(sdkItem.Name); - - if (sdkVersion.Major != netVersion.Major || - sdkVersion.Minor != netVersion.Minor) - continue; - - if (targetSdkVersion != null && - sdkVersion <= targetSdkVersion) - continue; - - targetSdkVersion = sdkVersion; - targetSdk = new RaspberryDebugger.Connection.Sdk(sdkItem.Name, sdkItem.Architecture); - } - - var sdkName = targetSdk?.Name; - + var netVersion = SemanticVersion.Parse(versionRegex.Match(moniker[1]).Groups["version"].Value); + // Load [Properties/launchSettings.json] if present to obtain the command line // arguments and environment variables as well as the target connection. Note // that we're going to use the profile named for the project and ignore any others. @@ -137,7 +130,8 @@ public static ProjectProperties CopyFrom(Solution solution, EnvDTE.Project proje // // ASPNETCORE_SERVER.URLS=http://0.0.0.0: - var launchSettingsPath = Path.Combine(projectFolder ?? string.Empty, "Properties", "launchSettings.json"); + var launchSettingsPath = Path.Combine(fullPath ?? string.Empty, @"Properties/launchSettings.json"); + var activeDebugProfile = project.Properties.Item("ActiveDebugProfile").Value.ToString(); var commandLineArgs = new List(); var environmentVariables = new Dictionary(); var isAspNet = false; @@ -154,7 +148,7 @@ public static ProjectProperties CopyFrom(Solution solution, EnvDTE.Project proje { foreach (var profile in ((JObject)profiles.Value).Properties()) { - if (profile.Name == project.Name) + if (profile.Name == activeDebugProfile) { var profileObject = (JObject)profile.Value; var environmentVariablesObject = (JObject)profileObject.Property("environmentVariables")?.Value; @@ -266,17 +260,19 @@ string SetLaunchUrl(JObject profileObject) // Determine whether the referenced .NET Core SDK is currently supported. // The bitness is not important for this - we need only the SDK version - var sdk = sdkName == null - ? null - : PackageHelper.SdkCatalog.Items.Find(item => - SemanticVersion.Parse(item.Name) == SemanticVersion.Parse(sdkName)); + var isSupportedSdkVersion = netVersion.Major == 3 || netVersion.Major >= 6; - var isSupportedSdkVersion = sdk != null; + var platformTarget = (string)project.ConfigurationManager.ActiveConfiguration.Properties + .Item( "PlatformTarget" ).Value; // Determine whether the project is Raspberry compatible. var isRaspberryCompatible = isNetCore && outputType == 1 && // 1=EXE - isSupportedSdkVersion; + isSupportedSdkVersion && + platformTarget.Contains( "arm" ); + + // linux-arm64 + var runtimeIdentifier = string.IsNullOrEmpty( platformTarget ) ? platformTarget : $"linux-{platformTarget}"; // We need to jump through some hoops to obtain the project GUID. var solutionService = RaspberryDebuggerPackage.Instance.SolutionService; @@ -288,14 +284,18 @@ string SetLaunchUrl(JObject profileObject) return new ProjectProperties() { Name = project.Name, - FullPath = project.FullName, + FullPath = fullPath, + OutputPath = project.ConfigurationManager.ActiveConfiguration.Properties.Item("OutputPath").Value.ToString(), + OutputFileName = project.Properties.Item("OutputFileName").Value.ToString(), Guid = projectGuid, - Configuration = project.ConfigurationManager.ActiveConfiguration.ConfigurationName, + ConfigurationName = project.ConfigurationManager.ActiveConfiguration.ConfigurationName, + PlatformName = project.ConfigurationManager.ActiveConfiguration.PlatformName, + ActiveDebugProfile = activeDebugProfile, IsNetCore = isNetCore, - SdkVersion = sdk?.Name, - OutputFolder = Path.Combine(projectFolder, project.ConfigurationManager.ActiveConfiguration.Properties.Item("OutputPath").Value.ToString()), - OutputFileName = (string)project.Properties.Item("OutputFileName").Value, + TargetFramework = targetFramework, + SdkVersion = new Version( netVersion.Major, netVersion.Minor), IsExecutable = outputType == 1, // 1=EXE + RuntimeIdentifier = runtimeIdentifier, AssemblyName = project.Properties.Item("AssemblyName").Value.ToString(), DebugEnabled = debugEnabled, DebugConnectionName = debugConnectionName, @@ -443,6 +443,11 @@ private static List ParseArgs(string commandLine) /// public string FullPath { get; private set; } + /// + /// Returns the relative path to the output directory. . + /// + public string OutputPath { get; private set; } + /// /// Returns the project's GUID. /// @@ -458,7 +463,7 @@ private static List ParseArgs(string commandLine) /// just the major and minor versions of the SDK. This may also return null /// if the SDK version could not be identified. /// - public string SdkVersion { get; private set; } + public Version SdkVersion { get; private set; } /// /// Returns true if the project references a supported .NET Core SDK version. @@ -468,12 +473,17 @@ private static List ParseArgs(string commandLine) /// /// Returns the project's build configuration. /// - public string Configuration { get; private set; } + public string ConfigurationName { get; private set; } /// - /// Returns the fully qualified path to the project's output directory. + /// Returns the project's build platform. /// - public string OutputFolder { get; private set; } + public string PlatformName { get; private set; } + + /// + /// Returns the Selected Debug profile. + /// + public string ActiveDebugProfile { get; private set; } /// /// Indicates that the program is an executable as opposed to something @@ -482,19 +492,24 @@ private static List ParseArgs(string commandLine) public bool IsExecutable { get; private set; } /// - /// Returns the publish runtime. + /// Returns the derived Runtime Identifier. /// - public string Runtime => "linux-arm"; + public string RuntimeIdentifier { get; set; } /// /// Returns the framework version. /// - public string Framework => null; + public DotNetFrameworks? TargetFramework { get; set; } = null; + + /// + /// Returns the publication folder. + /// + public string PublishFolder => Path.Combine(this.FullPath, this.OutputPath, this.RuntimeIdentifier); /// /// Returns the publication folder. /// - public string PublishFolder => Path.Combine(OutputFolder, Runtime); + public string OutputFolder => Path.Combine(this.FullPath, this.OutputPath); /// /// Returns the name of the output assembly. @@ -504,7 +519,7 @@ private static List ParseArgs(string commandLine) /// /// Returns the name of the output binary file. /// - private string OutputFileName { get; set; } + public string OutputFileName { get; private set; } /// /// Indicates whether Raspberry debugging is enabled for this project. diff --git a/RaspberryDebugger/PackageHelper.cs b/RaspberryDebugger/PackageHelper.cs index 3f5bb72..528eff1 100644 --- a/RaspberryDebugger/PackageHelper.cs +++ b/RaspberryDebugger/PackageHelper.cs @@ -1,745 +1,694 @@ -//----------------------------------------------------------------------------- -// FILE: PackageHelper.cs -// CONTRIBUTOR: Jeff Lill -// COPYRIGHT: Copyright (c) 2021 by neonFORGE, LLC. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -using System; -using System.IO; -using System.Linq; -using System.Reflection; -using System.Diagnostics; -using System.Windows.Forms; -using System.Threading.Tasks; -using System.Collections.Generic; -using System.Diagnostics.Contracts; - -using Microsoft.VisualStudio.Shell; -using Microsoft.VisualStudio.Shell.Interop; - -using EnvDTE; -using EnvDTE80; -using Microsoft.VisualStudio.Threading; -using Neon.IO; -using Neon.Common; - -using Newtonsoft.Json; -using Newtonsoft.Json.Converters; - -using RaspberryDebugger.Dialogs; -using RaspberryDebugger.Extensions; -using RaspberryDebugger.Models.Sdk; -using RaspberryDebugger.Models.Connection; -using RaspberryDebugger.Models.Project; -using RaspberryDebugger.Models.VisualStudio; -using VersionsService = RaspberryDebugger.Web; - -namespace RaspberryDebugger -{ - /// - /// Package specific constants. - /// - internal static class PackageHelper - { - private static SdkCatalog _cachedSdkCatalog; - - /// - /// The path to the folder holding the Raspberry SSH private keys. - /// - public static readonly string KeysFolder; - - /// - /// The path to the JSON file defining the Raspberry Pi connections. - /// - private static readonly string ConnectionsPath; - - /// - /// The name used to prefix logged output and status bar text. - /// - public const string LogName = "raspberry"; - - /// - /// Directory on the Raspberry Pi where .NET Core SDKs will be installed along with the - /// vsdbg remote debugger. - /// - public const string RemoteDotnetFolder = "/lib/dotnet"; - - /// - /// Fully qualified path to the dotnet executable on the Raspberry. - /// - public const string RemoteDotnetCommand = "/lib/dotnet/dotnet"; - - /// - /// Directory on the Raspberry Pi where the vsdbg remote debugger will be installed. - /// - public const string RemoteDebuggerFolder = RemoteDotnetFolder + "/vsdbg"; - - /// - /// Path to the vsdbg program on the remote machine. - /// - public const string RemoteDebuggerPath = RemoteDebuggerFolder + "/vsdbg"; - - /// - /// Returns the root directory on the Raspberry Pi where the folder where - /// program binaries will be uploaded for the named user. Each program will - /// have a sub directory named for the program. - /// - public static string RemoteDebugBinaryRoot(string username) - { - Covenant.Requires(!string.IsNullOrEmpty(username), nameof(username)); - - return LinuxPath.Combine("/", "home", username, "vsdbg"); - } - - /// - /// Returns information about the all good .NET Core SDKs, including the unusable ones. - /// - public static SdkCatalog SdkCatalog - { - get - { - if (_cachedSdkCatalog != null) return _cachedSdkCatalog; - - // read newest .net sdks - if(!ReadSdkCatalogToCache()) - { - // if no SDKs present show a message - MessageBoxEx.Show( - "Cannot find any SDK on page: https://dotnet.microsoft.com/en-us/download/dotnet", - "No SDK found", - MessageBoxButtons.OK, - MessageBoxIcon.Error); - } - - return _cachedSdkCatalog; - } - } - - /// - /// Read SDK links from hosted service - /// with fallback to sdk-catalog.json entries - /// - /// true if SDKs present - private static bool ReadSdkCatalogToCache() - { - using (new CursorWait()) - { - try - { - // try to get the catalog thru version feed service - _cachedSdkCatalog = JsonConvert.DeserializeObject( - ThreadHelper.JoinableTaskFactory.Run(async () => - await new VersionsService.Feed() - .ReadAsync() - .WithTimeout(TimeSpan.FromSeconds(2)))); - } - catch (Exception) - { - _cachedSdkCatalog = new SdkCatalog(); - } - - if (_cachedSdkCatalog?.Items.Any() == true) return true; - - _cachedSdkCatalog = ReadIntegratedCatalog(); - } - - return true; - } - - /// - /// Read assembly integrated catalog json - /// - /// SdkCatalog with SDK items - private static SdkCatalog ReadIntegratedCatalog() - { - try - { - // try to get the catalog thru own fetch - using var catalogStream = Assembly - .GetExecutingAssembly() - .GetManifestResourceStream("RaspberryDebugger.sdk-catalog.json"); - - var jsonSerializerSettings = new JsonSerializerSettings(); - jsonSerializerSettings.Converters.Add(new StringEnumConverter()); - - return JsonConvert.DeserializeObject( - new StreamReader(catalogStream!).ReadToEnd(), - jsonSerializerSettings); - } - catch (Exception) - { - _cachedSdkCatalog = new SdkCatalog(); - } - - return _cachedSdkCatalog; - } - - /// - /// Static constructor. - /// - static PackageHelper() - { - // Initialize the settings path and folders. - var settingsFolder = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".raspberry"); - - if (!Directory.Exists(settingsFolder)) - { - Directory.CreateDirectory(settingsFolder); - } - - KeysFolder = Path.Combine(settingsFolder, "keys"); - - if (!Directory.Exists(KeysFolder)) - { - Directory.CreateDirectory(KeysFolder); - } - - ConnectionsPath = Path.Combine(settingsFolder, "connections.json"); - } - - /// - /// Reads the persisted connection settings. - /// - /// Optionally disable logging. - /// The connections. - public static List ReadConnections(bool disableLogging = false) - { - if (!disableLogging) - { - Log.Info("Reading connections"); - } - - try - { - if (!File.Exists(ConnectionsPath)) - { - return new List(); - } - - var connections = NeonHelper.JsonDeserialize>(File.ReadAllText(ConnectionsPath)) ?? - new List(); - - // Ensure that at least one connection is marked as default. We'll - // select the first one as sorted by name if necessary. - if (connections.Count > 0 && !connections.Any(connection => connection.IsDefault)) - { - connections.OrderBy(connection => connection.Name - .ToLowerInvariant()) - .Single().IsDefault = true; - } - - return connections; - } - catch (Exception e) - { - if (!disableLogging) - { - Log.Exception(e); - } - - throw; - } - } - - /// - /// Persists the connections passed. - /// - /// The connections. - /// Optionally disable logging. - public static void WriteConnections(List connections, bool disableLogging = false) - { - if (!disableLogging) - { - Log.Info("Writing connections"); - } - - try - { - connections ??= new List(); - - // Ensure that at least one connection is marked as default. We'll - // select the first one as sorted by name if necessary. - if (connections.Count > 0 && !connections.Any(connection => connection.IsDefault)) - { - connections.OrderBy(connection => connection.Name.ToLowerInvariant()).First().IsDefault = true; - } - - File.WriteAllText(ConnectionsPath, NeonHelper.JsonSerialize(connections, Formatting.Indented)); - } - catch (Exception e) - { - if (!disableLogging) Log.Exception(e); - - throw; - } - } - - /// - /// Returns the current Visual Studio startup project for a solution. - /// - /// The current solution (or null). - /// The startup project or null. - /// - /// - /// The active project may be different from the startup project. Users select - /// the startup project explicitly and that project will remain selected until - /// the user selects another. The active project is determined by the current - /// document. - /// - /// - public static Project GetStartupProject(Solution solution) - { - ThreadHelper.ThrowIfNotOnUIThread(); - - if (solution?.SolutionBuild?.StartupProjects == null) - { - return null; - } - - var projectName = (string)((object[])solution.SolutionBuild.StartupProjects).FirstOrDefault(); - var startupProject = (Project)null; - - foreach (Project project in solution.Projects) - { - if (project.UniqueName == projectName) - { - startupProject = project; - } - else if (project.Kind == EnvDTE.Constants.vsProjectKindSolutionItems) - { - startupProject = FindInSubProjects(project, projectName); - } - - if (startupProject != null) - { - break; - } - } - - return startupProject; - } - - /// - /// Returns a solution's active project. - /// - /// The active or null for none. - /// - /// - /// The active project may be different from the startup project. Users select - /// the startup project explicitly and that project will remain selected until - /// the user selects another. The active project is determined by the current - /// document. - /// - /// - private static Project GetActiveProject(DTE2 dte) - { - Covenant.Requires(dte != null, nameof(dte)); - - ThreadHelper.ThrowIfNotOnUIThread(); - - var activeSolutionProjects = (Array)dte?.ActiveSolutionProjects; - - return activeSolutionProjects is { Length: > 0 } - ? (Project)activeSolutionProjects.GetValue(0) - : null; - } - - /// - /// Determines whether the active project is a candidate for debugging on - /// a Raspberry. Currently, the project must target .NET Core 3.1 or - /// greater and be an executable. - /// - /// - /// - /// true if there's an active project and it satisfies the criterion. - /// - public static bool IsActiveProjectRaspberryCompatible(DTE2 dte) - { - Covenant.Requires(dte != null, nameof(dte)); - ThreadHelper.ThrowIfNotOnUIThread(); - - var activeProject = GetActiveProject(dte); - - if (activeProject == null) - { - return false; - } - - var projectProperties = ProjectProperties.CopyFrom(dte?.Solution, activeProject); - - return projectProperties.IsRaspberryCompatible; - } - - /// - /// Searches a project's sub project for a project matching a path. - /// - /// The parent project. - /// The desired project name. - /// The or null. - private static Project FindInSubProjects(Project parentProject, string projectName) - { - ThreadHelper.ThrowIfNotOnUIThread(); - - if (parentProject == null) - { - return null; - } - - if (parentProject.UniqueName == projectName) - { - return parentProject; - } - - if (parentProject.Kind != EnvDTE.Constants.vsProjectKindSolutionItems) return null; - var project = (Project)null; - - // The project is actually a solution folder so recursively - // search any sub projects. - - foreach (ProjectItem projectItem in parentProject.ProjectItems) - { - if (projectItem.SubProject == null) continue; - project = FindInSubProjects(projectItem.SubProject, projectName); - - if (project != null) - { - break; - } - } - - return project; - } - - /// - /// Adds any projects suitable for debugging on a Raspberry to the - /// list, recursing into projects that are actually solution folders as required. - /// - /// The list where discovered projects will be added. - /// The parent solution. - /// The project or solution folder. - private static void GetSolutionProjects(List solutionProjects, Solution solution, Project project) - { - Covenant.Requires(solutionProjects != null, nameof(solutionProjects)); - Covenant.Requires(solution != null, nameof(solution)); - Covenant.Requires(project != null, nameof(project)); - - ThreadHelper.ThrowIfNotOnUIThread(); - - if (project?.Kind == EnvDTE.Constants.vsProjectKindSolutionItems) - { - foreach (ProjectItem projectItem in project.ProjectItems) - { - GetSolutionProjects(solutionProjects, solution, projectItem.SubProject); - } - } - else - { - var projectProperties = ProjectProperties.CopyFrom(solution, project); - - if (projectProperties.IsRaspberryCompatible) - { - solutionProjects?.Add(project); - } - } - } - - /// - /// Returns the path to the $/.vs/raspberry-projects.json file for - /// the current solution. - /// - /// The current solution. - /// The file path. - private static string GetRaspberryProjectsPath(Solution solution) - { - Covenant.Requires(solution != null); - ThreadHelper.ThrowIfNotOnUIThread(); - - return Path.Combine(Path.GetDirectoryName(solution?.FullName) ?? string.Empty, ".vs", "raspberry-projects.json"); - } - - /// - /// Reads the $/.vs/raspberry-projects.json file from the current - /// solution's directory. - /// - /// The current solution. - /// The projects read or an object with no projects if the file doesn't exist. - public static RaspberryProjects ReadRaspberryProjects(Solution solution) - { - Covenant.Requires(solution != null); - - ThreadHelper.ThrowIfNotOnUIThread(); - - var path = GetRaspberryProjectsPath(solution); - - return File.Exists(path) - ? NeonHelper.JsonDeserialize(File.ReadAllText(path)) - : new RaspberryProjects(); - } - - /// - /// Persists the project information passed to the $/.vs/raspberry-projects.json file. - /// - /// The current solution. - /// The projects. - public static void WriteRaspberryProjects(Solution solution, RaspberryProjects projects) - { - Covenant.Requires(solution != null); - Covenant.Requires(projects != null); - - ThreadHelper.ThrowIfNotOnUIThread(); - - // Prune any projects with GUIDs that are no longer present in - // the solution so these don't accumulate. Note that we need to - // recurse into solution folders to look for any projects there. - var solutionProjects = new List(); - - if (solution?.Projects != null) - { - foreach (Project project in solution.Projects) - { - GetSolutionProjects(solutionProjects, solution, project); - } - } - - var solutionProjectIds = new HashSet(); - var delList = new List(); - - foreach (var project in solutionProjects) - { - solutionProjectIds.Add(project.UniqueName); - } - - if (projects?.Keys != null) - { - delList.AddRange((projects.Keys).Where(projectId => !solutionProjectIds.Contains(projectId))); - } - - foreach (var projectId in delList) - { - projects?.Remove(projectId); - } - - // Write the file, ensuring that the parent directories exist. - var path = GetRaspberryProjectsPath(solution); - - Directory.CreateDirectory(Path.GetDirectoryName(path) ?? string.Empty); - File.WriteAllText(path, NeonHelper.JsonSerialize(projects, Formatting.Indented)); - } - - /// - /// Returns the project settings for a specific project. - /// - /// The current solution. - /// The target project. - /// The project settings. - public static ProjectSettings GetProjectSettings(Solution solution, Project project) - { - Covenant.Requires(solution != null); - Covenant.Requires(project != null); - - ThreadHelper.ThrowIfNotOnUIThread(); - var raspberryProjects = ReadRaspberryProjects(solution); - - return raspberryProjects[project?.UniqueName]; - } - - //--------------------------------------------------------------------- - // Progress related code - private const string ProgressCaption = "Raspberry Debugger"; - - private static IVsThreadedWaitDialog2 _progressDialog; - private static readonly Stack OperationStack = new(); - private static string _rootDescription; - - /// - /// Executes an asynchronous action that does not return a result within the context of a - /// Visual Studio progress dialog. You may make nested calls and this may also be called - /// from any thread. - /// - /// The operation description. - /// The action. - /// The tracking . - public static async Task ExecuteWithProgressAsync(string description, Func action) - { - Covenant.Requires(!string.IsNullOrEmpty(description), nameof(description)); - Covenant.Requires(action != null, nameof(action)); - - await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); - - if (_progressDialog == null) - { - Covenant.Assert(OperationStack.Count == 0); - - _rootDescription = description; - OperationStack.Push(description); - - var dialogFactory = (IVsThreadedWaitDialogFactory)Package - .GetGlobalService((typeof(SVsThreadedWaitDialogFactory))); - - dialogFactory.CreateInstance(out _progressDialog); - - _progressDialog.StartWaitDialog( - szWaitCaption: ProgressCaption, - szWaitMessage: description, - szProgressText: null, - varStatusBmpAnim: null, - szStatusBarText: null, - iDelayToShowDialog: 0, - fIsCancelable: false, - fShowMarqueeProgress: true); - } - else - { - Covenant.Assert(OperationStack.Count > 0); - - OperationStack.Push(description); - - _progressDialog.UpdateProgress( - szUpdatedWaitMessage: ProgressCaption, - szProgressText: description, - szStatusBarText: null, - iCurrentStep: 0, - iTotalSteps: 0, - fDisableCancel: true, - pfCanceled: out _); - } - - var orgCursor = Cursor.Current; - - try - { - Cursor.Current = Cursors.WaitCursor; - - if (action != null) await action().ConfigureAwait(false); - } - finally - { - await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); - - Cursor.Current = orgCursor; - - OperationStack.Pop(); - - if (OperationStack.Count == 0) - { - _progressDialog.EndWaitDialog(out _); - - _progressDialog = null; - _rootDescription = null; - } - else - { - _progressDialog.UpdateProgress( - szUpdatedWaitMessage: ProgressCaption, - szProgressText: description, - szStatusBarText: null, - iCurrentStep: 0, - iTotalSteps: 0, - fDisableCancel: true, - pfCanceled: out _); - } - } - } - - /// - /// Executes an asynchronous action that does not return a result within the context of a - /// Visual Studio progress dialog. You may make nested calls and this may also be called - /// from any thread. - /// - /// The action result type. - /// The operation description. - /// The action. - /// The tracking . - public static async Task ExecuteWithProgressAsync(string description, Func> action) - { - Covenant.Requires(!string.IsNullOrEmpty(description), nameof(description)); - Covenant.Requires(action != null, nameof(action)); - - await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); - - if (_progressDialog == null) - { - Covenant.Assert(OperationStack.Count == 0); - - _rootDescription = description; - OperationStack.Push(description); - - var dialogFactory = (IVsThreadedWaitDialogFactory)Package - .GetGlobalService((typeof(SVsThreadedWaitDialogFactory))); - - dialogFactory.CreateInstance(out _progressDialog); - - _progressDialog.StartWaitDialog( - szWaitCaption: ProgressCaption, - szWaitMessage: description, - szProgressText: null, - varStatusBmpAnim: null, - szStatusBarText: $"[{LogName}]{description}", - iDelayToShowDialog: 0, - fIsCancelable: false, - fShowMarqueeProgress: true); - } - else - { - Covenant.Assert(OperationStack.Count > 0); - - OperationStack.Push(description); - - _progressDialog.UpdateProgress( - szUpdatedWaitMessage: ProgressCaption, - szProgressText: description, - szStatusBarText: null, - iCurrentStep: 0, - iTotalSteps: 0, - fDisableCancel: true, - pfCanceled: out _); - } - - var orgCursor = Cursor.Current; - - try - { - Cursor.Current = Cursors.WaitCursor; - Debug.Assert(action != null, nameof(action) + " != null"); - return await action().ConfigureAwait(false); - } - finally - { - await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); - - Cursor.Current = orgCursor; - - var currentDescription = OperationStack.Pop(); - - if (OperationStack.Count == 0) - { - _progressDialog.EndWaitDialog(out _); - - _progressDialog = null; - _rootDescription = null; - } - else - { - _progressDialog.UpdateProgress( - szUpdatedWaitMessage: currentDescription, - szProgressText: null, - szStatusBarText: _rootDescription, - iCurrentStep: 0, - iTotalSteps: 0, - fDisableCancel: true, - pfCanceled: out _); - } - } - } - } +//----------------------------------------------------------------------------- +// FILE: PackageHelper.cs +// CONTRIBUTOR: Jeff Lill +// COPYRIGHT: Copyright (c) 2021 by neonFORGE, LLC. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +using System; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Diagnostics; +using System.Windows.Forms; +using System.Threading.Tasks; +using System.Collections.Generic; +using System.Diagnostics.Contracts; + +using Microsoft.VisualStudio.Shell; +using Microsoft.VisualStudio.Shell.Interop; + +using EnvDTE; +using EnvDTE80; +using Microsoft.VisualStudio.Threading; +using Neon.IO; +using Neon.Common; + +using Newtonsoft.Json; +using Newtonsoft.Json.Converters; + +using RaspberryDebugger.Dialogs; +using RaspberryDebugger.Extensions; +using RaspberryDebugger.Models.Sdk; +using RaspberryDebugger.Models.Connection; +using RaspberryDebugger.Models.Project; +using RaspberryDebugger.Models.VisualStudio; +using VersionsService = RaspberryDebugger.Web; + +namespace RaspberryDebugger +{ + enum VisualStudioVersions + { + Unknown, + + vs2017 = 15, + vs2019, + vs2022, + vs2026 + } + + /// + /// Package specific constants. + /// + internal static class PackageHelper + { + /// + /// The path to the folder holding the Raspberry SSH private keys. + /// + public static readonly string KeysFolder; + + /// + /// The path to the JSON file defining the Raspberry Pi connections. + /// + private static readonly string ConnectionsPath; + + /// + /// Indicates what VS application is currently running. + /// + private static VisualStudioVersions _visualStudioVersion = VisualStudioVersions.Unknown; + public static VisualStudioVersions VisualStudioVersion + { + get + { + if ( _visualStudioVersion == VisualStudioVersions.Unknown ) + { + ThreadHelper.ThrowIfNotOnUIThread(); + + DTE2 dte = (DTE2)Package.GetGlobalService(typeof(SDTE)); + + if (Version.TryParse(dte.Version, out Version result)) + { + Enum.TryParse(result.Major.ToString(), out _visualStudioVersion); + } + } + + return _visualStudioVersion; + } + } + + /// + /// Directory on the Raspberry Pi where .NET Core SDKs will be installed along with the + /// vsdbg remote debugger. + /// + public const string RemoteDotnetFolder = "/lib/dotnet"; + + /// + /// Fully qualified path to the dotnet executable on the Raspberry. + /// + public const string RemoteDotnetCommand = "/lib/dotnet/dotnet"; + + /// + /// Directory on the Raspberry Pi where the vsdbg remote debugger will be installed. + /// + public static readonly string RemoteDebuggerFolder = $"~/.vs-debugger/{VisualStudioVersion}"; + + /// + /// Path to the vsdbg program on the remote machine. + /// + public static string RemoteDebuggerPath = RemoteDebuggerFolder + "/vsdbg"; + + /// + /// Returns the root directory on the Raspberry Pi where the folder where + /// program binaries will be uploaded for the named user. Each program will + /// have a sub directory named for the program. + /// + public static string RemoteDebugBinaryRoot(string username) + { + Covenant.Requires(!string.IsNullOrEmpty(username), nameof(username)); + + return LinuxPath.Combine("/", "home", username, "vsdbg"); + } + + /// + /// Static constructor. + /// + static PackageHelper() + { + // Initialize the settings path and folders. + var settingsFolder = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".raspberry"); + + if (!Directory.Exists(settingsFolder)) + { + Directory.CreateDirectory(settingsFolder); + } + + KeysFolder = Path.Combine(settingsFolder, "keys"); + + if (!Directory.Exists(KeysFolder)) + { + Directory.CreateDirectory(KeysFolder); + } + + ConnectionsPath = Path.Combine(settingsFolder, "connections.json"); + } + + /// + /// Reads the persisted connection settings. + /// + /// Optionally disable logging. + /// The connections. + public static List ReadConnections(bool disableLogging = false) + { + if (!disableLogging) + { + Log.Info("Reading connections"); + } + + try + { + if (!File.Exists(ConnectionsPath)) + { + return new List(); + } + + var connections = NeonHelper.JsonDeserialize>(File.ReadAllText(ConnectionsPath)) ?? + new List(); + + // Ensure that at least one connection is marked as default. We'll + // select the first one as sorted by name if necessary. + if (connections.Count > 0 && !connections.Any(connection => connection.IsDefault)) + { + connections.OrderBy(connection => connection.Name + .ToLowerInvariant()) + .Single().IsDefault = true; + } + + return connections; + } + catch (Exception e) + { + if (!disableLogging) + { + Log.Exception(e); + } + + throw; + } + } + + /// + /// Persists the connections passed. + /// + /// The connections. + /// Optionally disable logging. + public static void WriteConnections(List connections, bool disableLogging = false) + { + if (!disableLogging) + { + Log.Info("Writing connections"); + } + + try + { + connections ??= new List(); + + // Ensure that at least one connection is marked as default. We'll + // select the first one as sorted by name if necessary. + if (connections.Count > 0 && !connections.Any(connection => connection.IsDefault)) + { + connections.OrderBy(connection => connection.Name.ToLowerInvariant()).First().IsDefault = true; + } + + File.WriteAllText(ConnectionsPath, NeonHelper.JsonSerialize(connections, Formatting.Indented)); + } + catch (Exception e) + { + if (!disableLogging) Log.Exception(e); + + throw; + } + } + + /// + /// Returns the current Visual Studio startup project for a solution. + /// + /// The current solution (or null). + /// The startup project or null. + /// + /// + /// The active project may be different from the startup project. Users select + /// the startup project explicitly and that project will remain selected until + /// the user selects another. The active project is determined by the current + /// document. + /// + /// + public static Project GetStartupProject(Solution solution) + { + ThreadHelper.ThrowIfNotOnUIThread(); + + if (solution?.SolutionBuild?.StartupProjects == null) + { + return null; + } + + var projectName = (string)((object[])solution.SolutionBuild.StartupProjects).FirstOrDefault(); + var startupProject = (Project)null; + + foreach (Project project in solution.Projects) + { + if (project.UniqueName == projectName) + { + startupProject = project; + } + else if (project.Kind == EnvDTE.Constants.vsProjectKindSolutionItems) + { + startupProject = FindInSubProjects(project, projectName); + } + + if (startupProject != null) + { + break; + } + } + + return startupProject; + } + + /// + /// Returns a solution's active project. + /// + /// The active or null for none. + /// + /// + /// The active project may be different from the startup project. Users select + /// the startup project explicitly and that project will remain selected until + /// the user selects another. The active project is determined by the current + /// document. + /// + /// + private static Project GetActiveProject(DTE2 dte) + { + Covenant.Requires(dte != null, nameof(dte)); + + ThreadHelper.ThrowIfNotOnUIThread(); + + var activeSolutionProjects = (Array)dte?.ActiveSolutionProjects; + + return activeSolutionProjects is { Length: > 0 } + ? (Project)activeSolutionProjects.GetValue(0) + : null; + } + + /// + /// Determines whether the active project is a candidate for debugging on + /// a Raspberry. Currently, the project must target .NET Core 3.1 or + /// greater and be an executable. + /// + /// + /// + /// true if there's an active project and it satisfies the criterion. + /// + public static bool IsActiveProjectRaspberryExecutable(DTE2 dte) + { + Covenant.Requires(dte != null, nameof(dte)); + ThreadHelper.ThrowIfNotOnUIThread(); + + var activeProject = GetActiveProject(dte); + + if (activeProject == null) + { + return false; + } + + var projectProperties = ProjectProperties.CopyFrom(dte?.Solution, activeProject); + + return projectProperties.IsNetCore && + projectProperties.IsExecutable && + projectProperties.IsSupportedSdkVersion; + } + + /// + /// Searches a project's sub project for a project matching a path. + /// + /// The parent project. + /// The desired project name. + /// The or null. + private static Project FindInSubProjects(Project parentProject, string projectName) + { + ThreadHelper.ThrowIfNotOnUIThread(); + + if (parentProject == null) + { + return null; + } + + if (parentProject.UniqueName == projectName) + { + return parentProject; + } + + if (parentProject.Kind != EnvDTE.Constants.vsProjectKindSolutionItems) return null; + var project = (Project)null; + + // The project is actually a solution folder so recursively + // search any sub projects. + + foreach (ProjectItem projectItem in parentProject.ProjectItems) + { + if (projectItem.SubProject == null) continue; + + project = FindInSubProjects(projectItem.SubProject, projectName); + + if (project != null) + { + break; + } + } + + return project; + } + + /// + /// Adds any projects suitable for debugging on a Raspberry to the + /// list, recursing into projects that are actually solution folders as required. + /// + /// The list where discovered projects will be added. + /// The parent solution. + /// The project or solution folder. + private static void GetSolutionProjects(List solutionProjects, Solution solution, Project project) + { + Covenant.Requires(solutionProjects != null, nameof(solutionProjects)); + Covenant.Requires(solution != null, nameof(solution)); + Covenant.Requires(project != null, nameof(project)); + + ThreadHelper.ThrowIfNotOnUIThread(); + + if (project?.Kind == EnvDTE.Constants.vsProjectKindSolutionItems) + { + foreach (ProjectItem projectItem in project.ProjectItems) + { + if ( projectItem.SubProject == null) continue; + + GetSolutionProjects(solutionProjects, solution, projectItem.SubProject); + } + } + else + { + var projectProperties = ProjectProperties.CopyFrom(solution, project); + + if (projectProperties.IsRaspberryCompatible) + { + solutionProjects?.Add(project); + } + } + } + + /// + /// Returns the path to the $/.vs/raspberry-projects.json file for + /// the current solution. + /// + /// The current solution. + /// The file path. + private static string GetRaspberryProjectsPath(Solution solution) + { + Covenant.Requires(solution != null); + ThreadHelper.ThrowIfNotOnUIThread(); + + return Path.Combine(Path.GetDirectoryName(solution?.FullName) ?? string.Empty, ".vs", "raspberry-projects.json"); + } + + /// + /// Reads the $/.vs/raspberry-projects.json file from the current + /// solution's directory. + /// + /// The current solution. + /// The projects read or an object with no projects if the file doesn't exist. + public static RaspberryProjects ReadRaspberryProjects(Solution solution) + { + Covenant.Requires(solution != null); + + ThreadHelper.ThrowIfNotOnUIThread(); + + var path = GetRaspberryProjectsPath(solution); + + return File.Exists(path) + ? NeonHelper.JsonDeserialize(File.ReadAllText(path)) + : new RaspberryProjects(); + } + + /// + /// Persists the project information passed to the $/.vs/raspberry-projects.json file. + /// + /// The current solution. + /// The projects. + public static void WriteRaspberryProjects(Solution solution, RaspberryProjects projects) + { + Covenant.Requires(solution != null); + Covenant.Requires(projects != null); + + ThreadHelper.ThrowIfNotOnUIThread(); + + // Prune any projects with GUIDs that are no longer present in + // the solution so these don't accumulate. Note that we need to + // recurse into solution folders to look for any projects there. + var solutionProjects = new List(); + + if (solution?.Projects != null) + { + foreach (Project project in solution.Projects) + { + GetSolutionProjects(solutionProjects, solution, project); + } + } + + var solutionProjectIds = new HashSet(); + var delList = new List(); + + foreach (var project in solutionProjects) + { + solutionProjectIds.Add(project.UniqueName); + } + + if (projects?.Keys != null) + { + delList.AddRange((projects.Keys).Where(projectId => !solutionProjectIds.Contains(projectId))); + } + + foreach (var projectId in delList) + { + projects?.Remove(projectId); + } + + // Write the file, ensuring that the parent directories exist. + var path = GetRaspberryProjectsPath(solution); + + Directory.CreateDirectory(Path.GetDirectoryName(path) ?? string.Empty); + File.WriteAllText(path, NeonHelper.JsonSerialize(projects, Formatting.Indented)); + } + + /// + /// Returns the project settings for a specific project. + /// + /// The current solution. + /// The target project. + /// The project settings. + public static ProjectSettings GetProjectSettings(Solution solution, Project project) + { + Covenant.Requires(solution != null); + Covenant.Requires(project != null); + + ThreadHelper.ThrowIfNotOnUIThread(); + var raspberryProjects = ReadRaspberryProjects(solution); + + return raspberryProjects[project?.UniqueName]; + } + + //--------------------------------------------------------------------- + // Progress related code + private const string ProgressCaption = "Raspberry Debugger"; + + private static IVsThreadedWaitDialog2 _progressDialog; + private static readonly Stack OperationStack = new(); + private static string _rootDescription; + + /// + /// Executes an asynchronous action that does not return a result within the context of a + /// Visual Studio progress dialog. You may make nested calls and this may also be called + /// from any thread. + /// + /// The operation description. + /// The action. + /// The tracking . + public static async Task ExecuteWithProgressAsync(string description, Func action) + { + Covenant.Requires(!string.IsNullOrEmpty(description), nameof(description)); + Covenant.Requires(action != null, nameof(action)); + + await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); + + if (_progressDialog == null) + { + Covenant.Assert(OperationStack.Count == 0); + + _rootDescription = description; + OperationStack.Push(description); + + var dialogFactory = (IVsThreadedWaitDialogFactory)Package + .GetGlobalService((typeof(SVsThreadedWaitDialogFactory))); + + dialogFactory.CreateInstance(out _progressDialog); + + _progressDialog.StartWaitDialog( + szWaitCaption: ProgressCaption, + szWaitMessage: description, + szProgressText: null, + varStatusBmpAnim: null, + szStatusBarText: null, + iDelayToShowDialog: 0, + fIsCancelable: false, + fShowMarqueeProgress: true); + } + else + { + Covenant.Assert(OperationStack.Count > 0); + + OperationStack.Push(description); + + _progressDialog.UpdateProgress( + szUpdatedWaitMessage: ProgressCaption, + szProgressText: description, + szStatusBarText: null, + iCurrentStep: 0, + iTotalSteps: 0, + fDisableCancel: true, + pfCanceled: out _); + } + + var orgCursor = Cursor.Current; + + try + { + Cursor.Current = Cursors.WaitCursor; + + if (action != null) await action().ConfigureAwait(false); + } + finally + { + await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); + + Cursor.Current = orgCursor; + + OperationStack.Pop(); + + if (OperationStack.Count == 0) + { + _progressDialog.EndWaitDialog(out _); + + _progressDialog = null; + _rootDescription = null; + } + else + { + _progressDialog.UpdateProgress( + szUpdatedWaitMessage: ProgressCaption, + szProgressText: description, + szStatusBarText: null, + iCurrentStep: 0, + iTotalSteps: 0, + fDisableCancel: true, + pfCanceled: out _); + } + } + } + + /// + /// Executes an asynchronous action that does not return a result within the context of a + /// Visual Studio progress dialog. You may make nested calls and this may also be called + /// from any thread. + /// + /// The action result type. + /// The operation description. + /// The action. + /// The tracking . + public static async Task ExecuteWithProgressAsync(string description, Func> action) + { + Covenant.Requires(!string.IsNullOrEmpty(description), nameof(description)); + Covenant.Requires(action != null, nameof(action)); + + await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); + + if (_progressDialog == null) + { + Covenant.Assert(OperationStack.Count == 0); + + _rootDescription = description; + OperationStack.Push(description); + + var dialogFactory = (IVsThreadedWaitDialogFactory)Package + .GetGlobalService((typeof(SVsThreadedWaitDialogFactory))); + + dialogFactory.CreateInstance(out _progressDialog); + + _progressDialog.StartWaitDialog( + szWaitCaption: ProgressCaption, + szWaitMessage: description, + szProgressText: null, + varStatusBmpAnim: null, + szStatusBarText: $"[Raspberry Debugger]{description}", + iDelayToShowDialog: 0, + fIsCancelable: false, + fShowMarqueeProgress: true); + } + else + { + Covenant.Assert(OperationStack.Count > 0); + + OperationStack.Push(description); + + _progressDialog.UpdateProgress( + szUpdatedWaitMessage: ProgressCaption, + szProgressText: description, + szStatusBarText: null, + iCurrentStep: 0, + iTotalSteps: 0, + fDisableCancel: true, + pfCanceled: out _); + } + + var orgCursor = Cursor.Current; + + try + { + Cursor.Current = Cursors.WaitCursor; + Debug.Assert(action != null, nameof(action) + " != null"); + return await action().ConfigureAwait(false); + } + finally + { + await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); + + Cursor.Current = orgCursor; + + var currentDescription = OperationStack.Pop(); + + if (OperationStack.Count == 0) + { + _progressDialog.EndWaitDialog(out _); + + _progressDialog = null; + _rootDescription = null; + } + else + { + _progressDialog.UpdateProgress( + szUpdatedWaitMessage: currentDescription, + szProgressText: null, + szStatusBarText: _rootDescription, + iCurrentStep: 0, + iTotalSteps: 0, + fDisableCancel: true, + pfCanceled: out _); + } + } + } + } } \ No newline at end of file diff --git a/RaspberryDebugger/Properties/AssemblyInfo.cs b/RaspberryDebugger/Properties/AssemblyInfo.cs index 22bdae8..035ff33 100644 --- a/RaspberryDebugger/Properties/AssemblyInfo.cs +++ b/RaspberryDebugger/Properties/AssemblyInfo.cs @@ -34,16 +34,3 @@ // to COM components. If you need to access a type in this assembly from // COM, set the ComVisible attribute to true on that type. [assembly: ComVisible(false)] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -// [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("3.2.0.0")] -[assembly: AssemblyFileVersion("3.2.0.0")] diff --git a/RaspberryDebugger/RaspberryDebugger.csproj b/RaspberryDebugger/RaspberryDebugger.csproj index 7350d32..d09803e 100644 --- a/RaspberryDebugger/RaspberryDebugger.csproj +++ b/RaspberryDebugger/RaspberryDebugger.csproj @@ -1,231 +1,256 @@ - - - - 16.0 - $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) - - - false - - - latest - - - latest - - - - Debug - AnyCPU - 2.0 - {82b43b9b-a64c-4715-b499-d71e9ca2bd60};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} - {80D6A0E0-A145-488C-976D-CE34A16D8533} - Library - Properties - RaspberryDebugger - RaspberryDebugger - v4.7.2 - true - true - true - false - false - true - true - Program - $(DevEnvDir)devenv.exe - /rootsuffix Exp - - - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - CS0414 - latest - - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - CS0414 - latest - - - - - - - - - - - - - - Form - - - ConnectionDialog.cs - - - Form - - - KeysDialog.cs - - - - Form - - - SettingsDialog.cs - - - - - - - - - - - - - - - - - UserControl - - - ConnectionsPanel.cs - - - Component - - - - - - - - - KeysDialog.cs - - - SettingsDialog.cs - - - - Always - true - - - - Designer - - - - - - - - - - - - - - - - - - - - - 15.0.6142705 - - - 17.2.32505.113 - - - 15.8.243 - - - compile; build; native; contentfiles; analyzers; buildtransitive - - - 17.2.32505.113 - - - 16.3.36 - - - runtime; build; native; contentfiles; analyzers; buildtransitive - all - - - 2.18.2 - - - 2.18.2 - - - 2.18.2 - - - 13.0.1 - - - 2.9.1 - - - 7.2.3 - - - 2.11.35 - - - - - Menus.ctmenu - - - - - ConnectionsPanel.cs - - - ConnectionDialog.cs - - - - - - - - - - - Always - true - - - - - - + + + + 16.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + + + false + + + latest + + + latest + + + + Debug + AnyCPU + 2.0 + {82b43b9b-a64c-4715-b499-d71e9ca2bd60};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + {80D6A0E0-A145-488C-976D-CE34A16D8533} + Library + Properties + RaspberryDebugger + RaspberryDebugger + v4.7.2 + true + true + true + false + false + true + true + Program + $(DevEnvDir)devenv.exe + /rootsuffix Exp + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + CS0414 + latest + True + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + CS0414 + latest + + + + + + + + + + + + + Form + + + ConnectionDialog.cs + + + Form + + + KeysDialog.cs + + + + Form + + + SettingsDialog.cs + + + + + + + + + + + + + + + UserControl + + + ConnectionsPanel.cs + + + Component + + + + + + + + + KeysDialog.cs + + + SettingsDialog.cs + + + Always + true + + + + Designer + + + + + + + + + + + + + + + + + + + + + compile; build; native; contentfiles; analyzers; buildtransitive + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + all + + + + 13.0.4 + + + 2.9.1 + + + 8.6.5 + + + 2.22.23 + + + 2025.1.0 + + + + + Menus.ctmenu + + + + + ConnectionsPanel.cs + + + ConnectionDialog.cs + + + + + + + + + + + Always + true + + + + + {7b3c9089-7dcc-90dc-a824-b0eecd7c511c} + Neon.Common + + + {030c5dd8-990d-0dd3-2886-02cb01c171cb} + Neon.SSH + + + + + + + + + + + + ", + string.Format("", Version)); + File.WriteAllText(FilePath, updatedContent); + ]]> + + + + + $(BuildVersion) + + + + + + + + + + \ No newline at end of file diff --git a/RaspberryDebugger/RaspberryDebuggerPackage.cs b/RaspberryDebugger/RaspberryDebuggerPackage.cs index 3692565..f11c0f4 100644 --- a/RaspberryDebugger/RaspberryDebuggerPackage.cs +++ b/RaspberryDebugger/RaspberryDebuggerPackage.cs @@ -1,4 +1,4 @@ -//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- // FILE: RaspberryDebuggerPackage.cs // CONTRIBUTOR: Jeff Lill // COPYRIGHT: Copyright (c) 2021 by neonFORGE, LLC. All rights reserved. @@ -16,13 +16,13 @@ // limitations under the License. using System; using System.Collections.Generic; +using System.Diagnostics; using System.Diagnostics.Contracts; using System.Runtime.InteropServices; using System.Threading; using EnvDTE; using EnvDTE80; -using Microsoft.VisualStudio; using Microsoft.VisualStudio.Shell; using Microsoft.VisualStudio.Shell.Interop; using RaspberryDebugger.Commands; @@ -36,6 +36,8 @@ namespace RaspberryDebugger /// /// Implements a VSIX package that automates debugging C# .NET Core applications remotely /// on Raspberry Pi OS. + /// + /// C:\Program Files\Microsoft Visual Studio\2022\Professional\VSSDK\VisualStudioIntegration\Common\Inc /// [PackageRegistration(UseManagedResourcesOnly = true, AllowsBackgroundLoading = true)] [ProvideAutoLoad(UIContextGuids80.NoSolution, PackageAutoLoadFlags.BackgroundLoad)] @@ -74,6 +76,11 @@ internal sealed class RaspberryDebuggerPackage : AsyncPackage /// public static RaspberryDebuggerPackage Instance { get; private set; } + /// + /// Current VS version. + /// + public static Version VisualStudioVersion { get; private set; } + /// /// Logs text to the Visual Studio Debug output panel. /// @@ -123,13 +130,27 @@ public static void Log(string text) }); } + /// + /// Clear the log output pane + /// + public static void LogClear() + { + if (Instance == null || _debugPane == null) return; // Logging hasn't been initialized yet. + + _ = Instance.JoinableTaskFactory.RunAsync( + async () => + { + await Instance.JoinableTaskFactory.SwitchToMainThreadAsync( Instance.DisposalToken ); + _debugPane.Clear(); + } ); + } + //--------------------------------------------------------------------- // Instance members private DTE2 dte; private CommandEvents debugStartCommandEvent; private CommandEvents debugStartWithoutDebuggingCommandEvent; - private CommandEvents debugAttachToProcessCommandEvent; private CommandEvents debugRestartCommandEvent; #pragma warning disable IDE0051 // Remove unused private members private readonly bool debugMode = false; @@ -163,10 +184,11 @@ protected override async Task InitializeAsync(CancellationToken cancellationToke } // Initialize the log panel. - var debugWindow = Package.GetGlobalService(typeof(SVsOutputWindow)) as IVsOutputWindow; - var generalPaneGuid = VSConstants.GUID_OutWindowDebugPane; + _debugPane = GetOutputPane( Guid.NewGuid(), "Raspberry Debugger" ); - debugWindow?.GetPane(ref generalPaneGuid, out _debugPane); + // VS version + var devenvInfo = FileVersionInfo.GetVersionInfo(dte.FullName); + VisualStudioVersion = new Version(devenvInfo.ProductVersion); // Intercept the debugger commands and quickly decide whether the startup project is enabled // for Raspberry remote debugging so we can invoke our custom commands instead. We'll just @@ -174,19 +196,16 @@ protected override async Task InitializeAsync(CancellationToken cancellationToke // debugging. debugStartCommandEvent = dte.Events.CommandEvents["{5EFC7975-14BC-11CF-9B2B-00AA00573819}", 0x0127]; debugStartWithoutDebuggingCommandEvent = dte.Events.CommandEvents["{5EFC7975-14BC-11CF-9B2B-00AA00573819}", 0x0170]; - debugAttachToProcessCommandEvent = dte.Events.CommandEvents["{5EFC7975-14BC-11CF-9B2B-00AA00573819}", 0x00d5]; debugRestartCommandEvent = dte.Events.CommandEvents["{5EFC7975-14BC-11CF-9B2B-00AA00573819}", 0x0128]; debugStartCommandEvent.BeforeExecute += DebugStartCommandEvent_BeforeExecute; debugStartWithoutDebuggingCommandEvent.BeforeExecute += DebugStartWithoutDebuggingCommandEvent_BeforeExecute; - debugAttachToProcessCommandEvent.BeforeExecute += AttachToProcessCommandEvent_BeforeExecute; debugRestartCommandEvent.BeforeExecute += DebugRestartCommandEvent_BeforeExecute; // Initialize the new commands. await SettingsCommand.InitializeAsync(this); await DebugStartCommand.InitializeAsync(this); await DebugStartWithoutDebuggingCommand.InitializeAsync(this); - await DebugAttachToProcessCommand.InitializeAsync(this); } /// @@ -198,11 +217,6 @@ protected override async Task InitializeAsync(CancellationToken cancellationToke //--------------------------------------------------------------------- // DEBUG Command interceptors - /// - /// Returns true if the IDE in debug mode. - /// - private bool IsDebugging => dte.Mode == vsIDEMode.vsIDEModeDebug; - /// /// Executes a command by command set GUID and command ID. /// @@ -270,7 +284,7 @@ private void DebugStartCommandEvent_BeforeExecute(string guid, int id, object cu { ThreadHelper.ThrowIfNotOnUIThread(); - if (IsDebugging) + if ( dte.Mode == vsIDEMode.vsIDEModeDebug) { return; } @@ -304,24 +318,6 @@ private void DebugStartWithoutDebuggingCommandEvent_BeforeExecute(string guid, i ExecuteCommand(DebugStartWithoutDebuggingCommand.CommandSet, DebugStartWithoutDebuggingCommand.CommandId); } - /// - /// Debug.AttachToProcess - /// - private void AttachToProcessCommandEvent_BeforeExecute(string guid, int id, object customIn, object customOut, ref bool cancelDefault) - { - ThreadHelper.ThrowIfNotOnUIThread(); - - var connectionName = GetConnectionName(); - - if (connectionName == null) - { - return; - } - - cancelDefault = true; - ExecuteCommand(DebugAttachToProcessCommand.CommandSet, DebugAttachToProcessCommand.CommandId); - } - /// /// Debug.Restart /// diff --git a/RaspberryDebugger/sdk-catalog.json b/RaspberryDebugger/sdk-catalog.json deleted file mode 100644 index 4ee5213..0000000 --- a/RaspberryDebugger/sdk-catalog.json +++ /dev/null @@ -1,1124 +0,0 @@ -{ - "items": [ - { - "name": "3.1", - "release": "3.1.425", - "architecture": "Arm32", - "link": "https://download.visualstudio.microsoft.com/download/pr/17fc9caa-076e-41c0-907c-f027140b081f/ac9ba7784bf06b63185ead3a2aa2f4f4/dotnet-sdk-3.1.425-linux-arm.tar.gz", - "sha512": "ca1d9e119dd72e3ed1afafd12b7e6b01ae98c1b5146008dff937d577426ff27069fcb912ab93bcbd064115eb69aca9ebc07b2fd84fccbed051c4f7c90a994df6" - }, - { - "name": "3.1", - "release": "3.1.424", - "architecture": "Arm32", - "link": "https://download.visualstudio.microsoft.com/download/pr/abae2803-1012-48e2-a720-355795f80d79/47650a0b203a6f28019fe6f68215528e/dotnet-sdk-3.1.424-linux-arm.tar.gz", - "sha512": "4ca319e7cdfcd318cb0d649d83a93f1ac87a7a777f454a0a71ff7e87bae1ef8020e789edb4bfa172c091edc66b1e5dce0faa0950cb95a93953863c76721ce11f" - }, - { - "name": "3.1", - "release": "3.1.423", - "architecture": "Arm32", - "link": "https://download.visualstudio.microsoft.com/download/pr/8f81b133-220b-4831-abe6-e8be161fd9a2/1af75b5e2ca89af2a31cf9981a976832/dotnet-sdk-3.1.423-linux-arm.tar.gz", - "sha512": "6b615ec6c1d66280c44ff28de0532ff6a4c21c77caf188101b04bdd58e8935436cb2b049ad9d831799476d421e25795184615c7e1caff8e550855e2f6ed5efd9" - }, - { - "name": "3.1", - "release": "3.1.422", - "architecture": "Arm32", - "link": "https://download.visualstudio.microsoft.com/download/pr/e5ec7845-008a-4b7d-a247-c314f2407b8d/9117e05fa19f0217a030666df6b6eb9d/dotnet-sdk-3.1.422-linux-arm.tar.gz", - "sha512": "9cbccaf303f693657f797ae81eec2bd2ea55975b7ae71a8add04175a0104545208fa2f9c536b97d91fa48c6ea890678eb0772a448977bce4acbc97726ac47f83" - }, - { - "name": "3.1", - "release": "3.1.421", - "architecture": "Arm32", - "link": "https://download.visualstudio.microsoft.com/download/pr/564f08f3-230e-4476-8ebe-7e1f02dceae4/9da37442bde3f45300c65e9f71e1b24a/dotnet-sdk-3.1.421-linux-arm.tar.gz", - "sha512": "412b30ee45554de69844e4297354680fdc5111c270b5e41af73db212117ee101878b87eb4592bda4c314ec0d7406ba8ab449d83466267f78198ce09fc32ef425" - }, - { - "name": "3.1", - "release": "3.1.420", - "architecture": "Arm32", - "link": "https://download.visualstudio.microsoft.com/download/pr/78abce54-388a-418f-85e0-cf3c6a0e7d35/98d1ee575a0369c97c3d14ee0e1085a8/dotnet-sdk-3.1.420-linux-arm.tar.gz", - "sha512": "d61cc1a5dc345d4c32bb06bcce1e50d603103294783460c811a87635eb14fc51c58869456f3326cfb689fdb7b379d2410f5db5bf63332aff173814035a319c28" - }, - { - "name": "3.1", - "release": "3.1.419", - "architecture": "Arm32", - "link": "https://download.visualstudio.microsoft.com/download/pr/077c85dd-4bb4-45d9-8cc7-4913ab0307d1/444453aee99210cbd2b966fba8c11cb6/dotnet-sdk-3.1.419-linux-arm.tar.gz", - "sha512": "a89d857200f5ce4d65f662f1acd70f0d8a6eb7fda0ae7ca904cf7b50a6772378a42c7d11ce0dac27c522d642fee0ad67c9da64ce0e7edbe27f28df776b4a45d8" - }, - { - "name": "3.1", - "release": "3.1.418", - "architecture": "Arm32", - "link": "https://download.visualstudio.microsoft.com/download/pr/c2bd849e-394c-48b0-9955-0d625d0aca50/60549bead15b74667ffc76f18aa05641/dotnet-sdk-3.1.418-linux-arm.tar.gz", - "sha512": "a5edf84b01740767cd0ed2d7e8f84ed66bd786fc61bc93065d3125c8eac123e6c383e7155e5ead31e344b691e23788b240f812d5a5759fa0d764c0e336f2cc22" - }, - { - "name": "3.1", - "release": "3.1.417", - "architecture": "Arm32", - "link": "https://download.visualstudio.microsoft.com/download/pr/36a601f0-b88e-41ea-910b-4dde7b083e73/61ec8564d50fb391f9e3a10b226fdd0d/dotnet-sdk-3.1.417-linux-arm.tar.gz", - "sha512": "d68f9b130f4a516cb199c9010a42acdda0c9e8c705bce0e72e9854587ee54f5a017b1cea5b84f15dae057531a8a619cffffa1e79f3413d376ba7d7226407574c" - }, - { - "name": "3.1", - "release": "3.1.416", - "architecture": "Arm32", - "link": "https://download.visualstudio.microsoft.com/download/pr/1b2a2fb1-b04b-485b-8a25-caed97ebe601/0c6024a3814f664558dc39fc85b34192/dotnet-sdk-3.1.416-linux-arm.tar.gz", - "sha512": "33a6d64f466839cc30adef87909a2ff98ecdf6bb763b82a7951314ee8eded7dc210297f914d4aa0b9c0b101aa0c33da97cb15ff64c5f83f08b212b885d662e90" - }, - { - "name": "3.1", - "release": "3.1.415", - "architecture": "Arm32", - "link": "https://download.visualstudio.microsoft.com/download/pr/e401b906-4681-437b-a1c5-21a2d7e0f83c/824371926ae334ac264b91d8234b350c/dotnet-sdk-3.1.415-linux-arm.tar.gz", - "sha512": "b738f0b4455b926b8fafa9699525cebbf51eaad7bbe0e77267640a3fe33645ab4901552008de0a9916f55d0eff1de61c22ebd432a5a1fb5099f0cfa1b33a5e75" - }, - { - "name": "3.1", - "release": "3.1.414", - "architecture": "Arm32", - "link": "https://download.visualstudio.microsoft.com/download/pr/cefd43b6-16ac-4435-bcc6-594ebb0441cf/7d064f0f61c4174f620eafe97484e6cb/dotnet-sdk-3.1.414-linux-arm.tar.gz", - "sha512": "08c93b7948151fbe3db9fa105ed02bfefb510e437aa9ad418c60ba96b41a7e25f891a1d4308f51595a9aa385d4e9ca05cab8a803e75728fb70aa9bae30f37b67" - }, - { - "name": "3.1", - "release": "3.1.413", - "architecture": "Arm32", - "link": "https://download.visualstudio.microsoft.com/download/pr/40edd52f-b1ca-4f0c-8d50-34433202ce9d/2b8f5b881c239a706f271f010e56159c/dotnet-sdk-3.1.413-linux-arm.tar.gz", - "sha512": "31f395b1e48e9ba53d4dc63db7ff1ea38bdcb612a1d54b483cde22a009c48fbae0303779f42cee32db0e51bd953c8abfdaa1620a43a7fd84e1f8e937b6675d59" - }, - { - "name": "3.1", - "release": "3.1.412", - "architecture": "Arm32", - "link": "https://download.visualstudio.microsoft.com/download/pr/5aef7e5d-2112-4868-8d7c-4c82f04363c5/e4fe30dc136634001cf357f26c825506/dotnet-sdk-3.1.412-linux-arm.tar.gz", - "sha512": "3c66abcdef095db393ecd25501ba1e13c5a78b79ce65e6bd9ddcc3a559528eab21b0a95b39acb86e5f7f8565ae7a799e6c7e6d723121132110f93eb868cf81eb" - }, - { - "name": "3.1", - "release": "3.1.411", - "architecture": "Arm32", - "link": "https://download.visualstudio.microsoft.com/download/pr/c36515e8-b5eb-4501-bfda-555c16938673/4faa9e8f1e1f84f020e85ab3dbd4c306/dotnet-sdk-3.1.411-linux-arm.tar.gz", - "sha512": "a684155eb088e280ea4e8612e06b3ccd3d00a58ecd84d0971ee96ac0ec80c1d7333acf929590a886be5759eec33890bfd126152f740657fb9bbea09e9f96e97a" - }, - { - "name": "3.1", - "release": "3.1.410", - "architecture": "Arm32", - "link": "https://download.visualstudio.microsoft.com/download/pr/9f93cdf3-2f04-461b-8c42-dbd84b624878/e69a16b86c90ddaa52775673bde765f4/dotnet-sdk-3.1.410-linux-arm.tar.gz", - "sha512": "139c9b41a89af44bef74de27df31d4bf50e56e5edd2d76e501bcaab7fc39764c1d74ef142fdd4f077a21c84b550b65038a642a4c65bbe65c0e38c6ce7fb7ac25" - }, - { - "name": "3.1", - "release": "3.1.409", - "architecture": "Arm32", - "link": "https://download.visualstudio.microsoft.com/download/pr/58d0ebb7-c06d-4d9a-a69f-22dac06fb278/0ae7881b7007c13a8e325d54a8f87657/dotnet-sdk-3.1.409-linux-arm.tar.gz", - "sha512": "4908a84951a93acac80c6e7d2dff88b40b90fa079bfc0a02678a70c412a45f1146a3d344c218791edef4bb972d549accadcbfcdc721be0478b07db3a3336cf6d" - }, - { - "name": "3.1", - "release": "3.1.408", - "architecture": "Arm32", - "link": "https://download.visualstudio.microsoft.com/download/pr/614983bc-78eb-4673-b1ff-fe876660ae21/03523848937d401293a7abdb56a6a0e2/dotnet-sdk-3.1.408-linux-arm.tar.gz", - "sha512": "05335636d61067c647e15ced618ca3ba2460b5aaa76e1713281fda2995740ee49fe9d63ae71fa8ac51786653b2ea7da83f540cec742628f78201206e142de609" - }, - { - "name": "3.1", - "release": "3.1.407", - "architecture": "Arm32", - "link": "https://download.visualstudio.microsoft.com/download/pr/12a5b3f4-4960-4e1a-a82c-c782138807b2/62f01a300ac1007d2b62612fa1aa3f3d/dotnet-sdk-3.1.407-linux-arm.tar.gz", - "sha512": "a60a272673842470ec3b32addd2ce2c3528b36315ba9c0b4237847b7e0b34914cb56b4b2f62410de2ccf231656327a7a3bbe2afa5c14672f1808df5cb4f9b8d8" - }, - { - "name": "3.1", - "release": "3.1.406", - "architecture": "Arm32", - "link": "https://download.visualstudio.microsoft.com/download/pr/9863a55b-2577-49d3-9888-ab853a4201cb/3110704f3265713f8d82aab157a23ed2/dotnet-sdk-3.1.406-linux-arm.tar.gz", - "sha512": "b978d8f8dd6af16abb16f42b8c36c356d18e6e88309967ae9faa9a8009245e4a94183a4c2a6acad48ba342a7429de8055e4e32aca79f0c1d2f2c3bca1a318530" - }, - { - "name": "3.1", - "release": "3.1.405", - "architecture": "Arm32", - "link": "https://download.visualstudio.microsoft.com/download/pr/2178c8a1-ad48-4e51-9ddd-4e3ab64d1f0e/68746abefadf62be43ca525653c915a1/dotnet-sdk-3.1.405-linux-arm.tar.gz", - "sha512": "110f595a835ebc5c974fcb24e5ed7b81461288ddcf6b3a0dcead3f22b6995698dab767304e0bd98d3c450a297db8f04b185bbaa0d310fc9f1d3f9c3157fdac06" - }, - { - "name": "3.1", - "release": "3.1.404", - "architecture": "Arm32", - "link": "https://download.visualstudio.microsoft.com/download/pr/2ebe1f4b-4423-4694-8f5b-57f22a315d66/4bceeffda88fc6f19fad7dfb2cd30487/dotnet-sdk-3.1.404-linux-arm.tar.gz", - "sha512": "0aaed20c96c97fd51b8e0f525caf75ab95c5a51de561e76dc89dad5d3c18755c0c773b51be6f1f5b07dda02d2bb426c5b9c45bb5dd59914beb90199f41e5c59e" - }, - { - "name": "3.1", - "release": "3.1.403", - "architecture": "Arm32", - "link": "https://download.visualstudio.microsoft.com/download/pr/8a2da583-cac8-4490-bcca-2a3667d51142/6a0f7fb4b678904cdb79f3cd4d4767d5/dotnet-sdk-3.1.403-linux-arm.tar.gz", - "sha512": "9f1293ef8f3abf5c6a8da7f963de4408d5aed6a9ee29a2c84655871574041980d8a4bb5b608ea129b1bd392890dc577d11c138bd54ce91745cb4960be697cb4e" - }, - { - "name": "3.1", - "release": "3.1.402", - "architecture": "Arm32", - "link": "https://download.visualstudio.microsoft.com/download/pr/8f0dffe3-18f0-4d32-beb0-dbcb9a0d91a1/abe9a34e3f8916478f0bd80402b01b38/dotnet-sdk-3.1.402-linux-arm.tar.gz", - "sha512": "b4ada8bff572d39262c952506661d6f328d22dd963903ed81a1c7646e1dfbd54e8f6897e0ff189bfae29f69b76b3ddecc17001b5df1ddd9e8da4cf9eebdd22d7" - }, - { - "name": "3.1", - "release": "3.1.401", - "architecture": "Arm32", - "link": "https://download.visualstudio.microsoft.com/download/pr/a92a6358-52c3-472b-ad6d-d2d80abdcef4/37a7551a4e2c9e455caed5ef777a8983/dotnet-sdk-3.1.401-linux-arm.tar.gz", - "sha512": "2c84d8442fd872aafbdff7a1f131c4bacfb75ca69426d2aa2b9f3cf05a8e3a365923c14f7eb732113dd7a9fdc674c955e70817fb355aaabca25b79ed33356a15" - }, - { - "name": "3.1", - "release": "3.1.302", - "architecture": "Arm32", - "link": "https://download.visualstudio.microsoft.com/download/pr/56691c4c-341a-4bca-9869-409803d23cf8/d872d7a0c27a6c5e9b812e889de89956/dotnet-sdk-3.1.302-linux-arm.tar.gz", - "sha512": "eccb9ba89eb745282cb749e505fcf2209e3b56f41d5ddbb383dbeae04eb58a9b367560d743bc78600c8adab4abb93bbabccaae33613b9d3fec2b150fca5dffc4" - }, - { - "name": "3.1", - "release": "3.1.301", - "architecture": "Arm32", - "link": "https://download.visualstudio.microsoft.com/download/pr/dbf4ea18-70bf-4b0f-ae9c-65c8c88bcadd/115e84fb95170ddeeaf9bdb9222c964d/dotnet-sdk-3.1.301-linux-arm.tar.gz", - "sha512": "732364b93caaa94ee96dfe24ed42e63ae59862afd0691760557c22019c67139f92db28cc5e730618a630c45a96b2468676867345e54ae93004567b470edf5f47" - }, - { - "name": "3.1", - "release": "3.1.300", - "architecture": "Arm32", - "link": "https://download.visualstudio.microsoft.com/download/pr/f2e1cb4a-0c70-49b6-871c-ebdea5ebf09d/acb1ea0c0dbaface9e19796083fe1a6b/dotnet-sdk-3.1.300-linux-arm.tar.gz", - "sha512": "510de2931522633e5a35cfbaebac255704bb2a282e4980e7597c924531564b1a2f769cf67b3d1f196442ceca3d0d9a53e0a6dcb12adc9b0c6c1500742e7b1ee5" - }, - { - "name": "3.1", - "release": "3.1.202", - "architecture": "Arm32", - "link": "https://download.visualstudio.microsoft.com/download/pr/647e322c-e198-4bff-ba2a-a1a5b8477033/87b7b0af543dba7048a57ee2ab5a31ee/dotnet-sdk-3.1.202-linux-arm.tar.gz", - "sha512": "6a978081468458fb08f405e11d39067f2dab3ec1b9ed9d033f7e74ddf8e700eb15c9c3d86d8fba5dbcf44f03b06c7f4a5d84b531e7d57a75d7a4f4cdc7f98992" - }, - { - "name": "3.1", - "release": "3.1.201", - "architecture": "Arm32", - "link": "https://download.visualstudio.microsoft.com/download/pr/ccbcbf70-9911-40b1-a8cf-e018a13e720e/03c0621c6510f9c6f4cca6951f2cc1a4/dotnet-sdk-3.1.201-linux-arm.tar.gz", - "sha512": "f37d0e55c9f593c6951bea5a6bb1ea3194486956efe08a2a0f266b419d912cdcbf4ac279358976f0bfa1fe560c333ca5d5437f8e8c718bb5963991e4395e0cd7" - }, - { - "name": "3.1", - "release": "3.1.200", - "architecture": "Arm32", - "link": "https://download.visualstudio.microsoft.com/download/pr/21a124fd-5bb7-403f-bdd2-489f9d21d695/b58fa90d19a5a5124d21dea94422868c/dotnet-sdk-3.1.200-linux-arm.tar.gz", - "sha512": "5e8a5e5899cd3c635a4278c6c57b9f9c75ffee81734ccf85feaacf1c3799cc135fd24d401abb7e8783db40e2072769a1d2a012f3317ed39cc019d69712243266" - }, - { - "name": "3.1", - "release": "3.1.120", - "architecture": "Arm32", - "link": "https://download.visualstudio.microsoft.com/download/pr/ff0c5ae3-2b4d-422c-8619-2121a6fac5bc/a38c89439845fc4ef076b7232141e9c0/dotnet-sdk-3.1.120-linux-arm.tar.gz", - "sha512": "b82c36dac8a6aeba87e7015775bdffb1ed381abc86ed51a251d508f670015b8c720e65781b39566931dd7948f56735175c176445efb14ef36b82f7b620219746" - }, - { - "name": "3.1", - "release": "3.1.119", - "architecture": "Arm32", - "link": "https://download.visualstudio.microsoft.com/download/pr/2b395979-32e6-4154-bd52-007fb7b58ea1/a7558832505d59d3f0c444e2081304ae/dotnet-sdk-3.1.119-linux-arm.tar.gz", - "sha512": "1e8c67c0036d49c57d4525f256d422b40d315dc434dca9aad7ae6f164a17168d74ebd6cda81b974798bd4008b78b41eb5bd81a7092b2efd6ebb96feea3eac5e2" - }, - { - "name": "3.1", - "release": "3.1.118", - "architecture": "Arm32", - "link": "https://download.visualstudio.microsoft.com/download/pr/64a1b7b0-e5ca-4454-abfb-058b91b5a975/979872728452ea321d067351e526f08e/dotnet-sdk-3.1.118-linux-arm.tar.gz", - "sha512": "e7fe87cf3d5a4d4f20c2f1b7b4e0e8ae99bd14ece3113e9bcf83ab54cc93b40ee4458b7eb76d9f78f153a8b1aed5cab62398577e6fc4aa204f8827551112cb08" - }, - { - "name": "3.1", - "release": "3.1.117", - "architecture": "Arm32", - "link": "https://download.visualstudio.microsoft.com/download/pr/56bbadc1-12ad-4885-95e6-a52760aa4786/d5bc16bac8471d8c8a978c9dc6154bb0/dotnet-sdk-3.1.117-linux-arm.tar.gz", - "sha512": "c7a781fb1cccb308788d5b04c3acf822c3d0fd8df31a2882090b238a7d04ce9bf059b4153a0f6e0cc7e6468ff8fbe89ae3456b6303dcb8292157e7470f71442d" - }, - { - "name": "3.1", - "release": "3.1.116", - "architecture": "Arm32", - "link": "https://download.visualstudio.microsoft.com/download/pr/351c3281-43d6-4f2f-8936-6ba4f0ae9e0e/159e36b534b01a6ee8dd13d2e53680a8/dotnet-sdk-3.1.116-linux-arm.tar.gz", - "sha512": "3fd1a4842067a7dca2a6730865884b607dcf5bdd40406a9950448ccf1d12941446a2d753521c124b6ee6b9adb8cc4076da4a9b2f548f5afce52cbc96f8a9613e" - }, - { - "name": "3.1", - "release": "3.1.115", - "architecture": "Arm32", - "link": "https://download.visualstudio.microsoft.com/download/pr/31ccb908-2638-4fa1-8ed4-2e90ca89e42e/30dc805ef1b30c277b0a63a553439d41/dotnet-sdk-3.1.115-linux-arm.tar.gz", - "sha512": "00da62d108784d6fb09a9be418479ca695b99f4ef5f65c56a700f61bbe7eac8cb83cadd596fb4577c77a434b435493fa32b889b1c23375c82c391dce1385d004" - }, - { - "name": "3.1", - "release": "3.1.114", - "architecture": "Arm32", - "link": "https://download.visualstudio.microsoft.com/download/pr/09c1545d-dd80-4629-ad2d-90a69723cb0b/06fd6210be9220099d4a506c0323c7c1/dotnet-sdk-3.1.114-linux-arm.tar.gz", - "sha512": "3357a7a3192583b8478364d684e3c93e8dea894628b6fa9cffaba6fc9daabf9c8aada4f5cc423a56041d01e95fc9e703109f3ba6b320dc52ea3055f555e932fa" - }, - { - "name": "3.1", - "release": "3.1.113", - "architecture": "Arm32", - "link": "https://download.visualstudio.microsoft.com/download/pr/d0cb55c6-1731-4c64-aaa9-3e87cad0c890/515de52bd97e8d78bb837c3d19c54061/dotnet-sdk-3.1.113-linux-arm.tar.gz", - "sha512": "c27d63b171dda1e7513137c625ebb5897f7b9b10b4d213f49dd32e353913f18d112469eb19f739b8186d02a7f5cc47f050b38c000b109ca1233982b9556c3b87" - }, - { - "name": "3.1", - "release": "3.1.112", - "architecture": "Arm32", - "link": "https://download.visualstudio.microsoft.com/download/pr/d720b6ef-d5c3-4403-a87e-e371c33fbc08/3cbf4e48b65cb9f9dc9f8db0959eed1c/dotnet-sdk-3.1.112-linux-arm.tar.gz", - "sha512": "a7e639a7432ce4ef404ab8bbc66f42128571a1b7de70989c6aca26dd83a43d036db985f31992a62d707d7986eb00a30b03b67ed28e1f5048a76bb9954fbf97de" - }, - { - "name": "3.1", - "release": "3.1.111", - "architecture": "Arm32", - "link": "https://download.visualstudio.microsoft.com/download/pr/127a8331-eb7c-423e-b326-fa2227c159bb/c5d9c46055e41b1b2486a2e289b382f7/dotnet-sdk-3.1.111-linux-arm.tar.gz", - "sha512": "67b9aa755b208a9594d73b3964721521976963e05abc6cfc95add271d6c09a0848555a04d0aa1531d900d67aa3fb7414dc704af74ec5d733ee18b6267f3ca460" - }, - { - "name": "3.1", - "release": "3.1.110", - "architecture": "Arm32", - "link": "https://download.visualstudio.microsoft.com/download/pr/b4b0a624-a046-4ad5-a19b-82dd26c1ccdb/28af71681b300f3a8b7aa0841cce45cc/dotnet-sdk-3.1.110-linux-arm.tar.gz", - "sha512": "af345605f4f795d90ca469b70ded4a62d8d3536cc1cf8856a856b576a438c1faff9f8b652c4657d7ca211306266d78f80da6ea1286e00812314bd742786d72d4" - }, - { - "name": "3.1", - "release": "3.1.109", - "architecture": "Arm32", - "link": "https://download.visualstudio.microsoft.com/download/pr/d9a0b670-88f8-44ac-a6b1-9020c783c518/8bbec2b438275789bc1dbc95b8f89adf/dotnet-sdk-3.1.109-linux-arm.tar.gz", - "sha512": "035b00adc3aecc365017db44771a30300b0858c6eb480f7358fa5c9b931a1d7d2a8d3e44d88a3e7ef59efbdf1cfab1c9dbd52e8c31694a1fa9419805134e2b52" - }, - { - "name": "3.1", - "release": "3.1.108", - "architecture": "Arm32", - "link": "https://download.visualstudio.microsoft.com/download/pr/52fb935e-f2b3-42c3-9695-d53e7c693baa/6c38b8ac3bc97ea2f09a03a60cdacdc5/dotnet-sdk-3.1.108-linux-arm.tar.gz", - "sha512": "b2f074cb6820f8859c61e04a355087828028f5c11912e0d1bcb0f93c5f3597eb60774542b0f3f3955f81fb459784036f2d4a9bcfcc0ba06965d671ed004e52ab" - }, - { - "name": "3.1", - "release": "3.1.107", - "architecture": "Arm32", - "link": "https://download.visualstudio.microsoft.com/download/pr/41775681-e8a1-4cdd-b1d2-967c25d2b5d6/7b968b58d157f7159ec4b6466c76ff02/dotnet-sdk-3.1.107-linux-arm.tar.gz", - "sha512": "e36f8192127c6d52c34fe15dabc23d4b9cb6e48d88a44355f687d974b13fe4a56dcb142fcacd9d959e67469cc31455b10b1fad8a5fdebbfc3f6cebc99d63dc5d" - }, - { - "name": "3.1", - "release": "3.1.106", - "architecture": "Arm32", - "link": "https://download.visualstudio.microsoft.com/download/pr/ad10d24d-9823-42fc-bf2a-1d598d8999c6/80f944ded4c6d3fd73cd6a00f6ae660f/dotnet-sdk-3.1.106-linux-arm.tar.gz", - "sha512": "05830f3b9924706d40b9a310e5dc334029f7be080b0e69aaef17e470b9845e1cc708e52652537c2ead98ca4209fda4d53a0cd5200c6b7727ce42f808c54d50f6" - }, - { - "name": "3.1", - "release": "3.1.105", - "architecture": "Arm32", - "link": "https://download.visualstudio.microsoft.com/download/pr/52f61afc-0897-4bc7-abf4-f936f9c6f637/8fca0abb8c378a32d966c1b762524c0d/dotnet-sdk-3.1.105-linux-arm.tar.gz", - "sha512": "a5e7531445c5efc5d647a6dca3725bc13b92df19aa87b63163b01518de5eb29e22f02c124807f29edc5f07ab31c56b675a6c34a1eae50e090c4ddf930fb4bd9f" - }, - { - "name": "3.1", - "release": "3.1.104", - "architecture": "Arm32", - "link": "https://download.visualstudio.microsoft.com/download/pr/74e61bc8-e8b1-4728-9c56-7d978010e4dd/a3c8533ed97dcfea09fe64e6969cd60c/dotnet-sdk-3.1.104-linux-arm.tar.gz", - "sha512": "18120f948651558f8b075017d42f075b97454c386ec9c7985f68169755a9b059110b5b52edebe8763ab0178ffd1bc453a70d9231d61213170b386ff899fc6463" - }, - { - "name": "3.1", - "release": "3.1.103", - "architecture": "Arm32", - "link": "https://download.visualstudio.microsoft.com/download/pr/7eb8b77b-e7e9-44ea-8a4e-e560ef639958/ee3565807f13ea0cf62e7a6bd8a58221/dotnet-sdk-3.1.103-linux-arm.tar.gz", - "sha512": "7b646a0e62b781f658862f3e6e2ada2e3ce5a3529b835d8c7fc1931c1b6654de89a152a7a033a3dc23352880684bd149bd4f6fda180a6da66d860ac25b578174" - }, - { - "name": "3.1", - "release": "3.1.102", - "architecture": "Arm32", - "link": "https://download.visualstudio.microsoft.com/download/pr/349f13f0-400e-476c-ba10-fe284b35b932/44a5863469051c5cf103129f1423ddb8/dotnet-sdk-3.1.102-linux-arm.tar.gz", - "sha512": "b279185fd245c3a254f4bbdd4f743d4309fcf8e2d3a1eb42df2f464e40df9954257209a666287ad3d924f863ca09d0cd8638d394da9f990477a2fc6d71198550" - }, - { - "name": "3.1", - "release": "3.1.101", - "architecture": "Arm32", - "link": "https://download.visualstudio.microsoft.com/download/pr/d52fa156-1555-41d5-a5eb-234305fbd470/173cddb039d613c8f007c9f74371f8bb/dotnet-sdk-3.1.101-linux-arm.tar.gz", - "sha512": "bd68786e16d59b18096658ccab2a662f35cd047065a6c87a9c6790a893a580a6aa81b1338360087e58d5b5e5fdca08269936281e41a7a7e7051667efb738a613" - }, - { - "name": "3.1", - "release": "3.1.100", - "architecture": "Arm32", - "link": "https://download.visualstudio.microsoft.com/download/pr/67766a96-eb8c-4cd2-bca4-ea63d2cc115c/7bf13840aa2ed88793b7315d5e0d74e6/dotnet-sdk-3.1.100-linux-arm.tar.gz", - "sha512": "9f4848b4bca649cc6131032de4b0115549a3dafb6bf90250930794aa69f7939bba82cedda67578348a83b50b7057e452846a400589bb4e9d4a2d1b33ce57c71c" - }, - { - "name": "3.1", - "release": "3.1.425", - "architecture": "Arm64", - "link": "https://download.visualstudio.microsoft.com/download/pr/d4a8d050-e3d0-4f07-b222-5cadb21320f2/05d4d832757a78ec88fb56d8f9f4cc65/dotnet-sdk-3.1.425-linux-arm64.tar.gz", - "sha512": "f3c18acc094c19f3887f6598c34c9a2e1cfa94055f77aa4deae7e51e8d760ca87af7185cc9ed102e08f04d35f9a558894f04f7a44fa56b91232ccc895f4e5a5c" - }, - { - "name": "3.1", - "release": "3.1.424", - "architecture": "Arm64", - "link": "https://download.visualstudio.microsoft.com/download/pr/dfe62f78-d4c1-4190-9d9d-44249e83a0c5/1fb0e84fb45e4e5d3207de6db85d99c3/dotnet-sdk-3.1.424-linux-arm64.tar.gz", - "sha512": "3bfd29233a3e0dfdbdc967f07808d4e239651f0f4f23f7c9e74f09271c9ded8044539ea4278bad070504ad782c4638a493bd9026ddbc97bbc657c5c12c27ccd2" - }, - { - "name": "3.1", - "release": "3.1.423", - "architecture": "Arm64", - "link": "https://download.visualstudio.microsoft.com/download/pr/11abab07-d7a2-46b0-9ab5-19d5db67212f/783196073ecbd9fd64378fec412affbe/dotnet-sdk-3.1.423-linux-arm64.tar.gz", - "sha512": "ba4f82e939be43ed863f059f69cdfb80b6dfe7cf99638bd6e787b060c2c1c4934440b599c133f61e3a0995f73675ae5d927bb047597cdd6a15b9074891dfd62e" - }, - { - "name": "3.1", - "release": "3.1.422", - "architecture": "Arm64", - "link": "https://download.visualstudio.microsoft.com/download/pr/fdf76122-e9d5-4f66-b96f-4dd0c64e5bea/d756ca70357442de960db145f9b4234d/dotnet-sdk-3.1.422-linux-arm64.tar.gz", - "sha512": "3eb7e066568dfc0135f2b3229d0259db90e1920bb413f7e175c9583570146ad593b50ac39c77fb67dd3f460b4621137f277c3b66c44206767b1d28e27bf47deb" - }, - { - "name": "3.1", - "release": "3.1.421", - "architecture": "Arm64", - "link": "https://download.visualstudio.microsoft.com/download/pr/3aa1ef77-157e-4c54-bdc6-1cf589f3e3d1/590f24f95d808144bc18caa0c45b7d9f/dotnet-sdk-3.1.421-linux-arm64.tar.gz", - "sha512": "c584642469343c2c54fa02a7157009fa36bae9b304512db0a2b0069f71593ee2ba47070896212def0541460f37bf1b0a478b914e08a2c78b985cb2981e5ab6c6" - }, - { - "name": "3.1", - "release": "3.1.420", - "architecture": "Arm64", - "link": "https://download.visualstudio.microsoft.com/download/pr/a84bf296-ee6e-4e66-9694-90d3da7af2b4/b00b2efe2432938e5a19c45d3759d80f/dotnet-sdk-3.1.420-linux-arm64.tar.gz", - "sha512": "ac66b1544fe178153bb85c2e5be584464374ce4c036fc95720547c231c2730312018fbdfc735f9071579749415bc54e1f6b8f080cc2b08d5799a0da941e8a5f5" - }, - { - "name": "3.1", - "release": "3.1.419", - "architecture": "Arm64", - "link": "https://download.visualstudio.microsoft.com/download/pr/6aaec6b7-3d0c-4f00-a8a0-a53c4844525b/e932447b5c2e854bcae25942a2113af2/dotnet-sdk-3.1.419-linux-arm64.tar.gz", - "sha512": "94f398c09b53c10dc3e4ed1f624eee19b18770734956ebb0cb4ac9d789c1a79a891c1934e7c4c3a2bed5326ee1a0417ee89816695ab2436b3db7076328a40b77" - }, - { - "name": "3.1", - "release": "3.1.418", - "architecture": "Arm64", - "link": "https://download.visualstudio.microsoft.com/download/pr/1533613a-1a50-48d0-bffe-341276bf95b3/d20d99d497f07b40f655b888319de311/dotnet-sdk-3.1.418-linux-arm64.tar.gz", - "sha512": "8c3f1254a27991f116f499b11aa389266bc63b93a85ab7103b398bdf14225755277499ccb8297012f572732e5e521c23d02fe3d99b552ecadf8af2867456ebc5" - }, - { - "name": "3.1", - "release": "3.1.417", - "architecture": "Arm64", - "link": "https://download.visualstudio.microsoft.com/download/pr/5da6dffe-5c27-4d62-87c7-a3fca48be9bd/967bd7ddc7bbcaef20671175f7b26ee3/dotnet-sdk-3.1.417-linux-arm64.tar.gz", - "sha512": "28ea17c3c8e57721fdafcdf8339a175d4d7c29616597d7dac60362fe4ac8c3a8493612865a37e985729b3d3953caae7fed2f8a11a0d0bb1dd24b1d816d6b6abf" - }, - { - "name": "3.1", - "release": "3.1.416", - "architecture": "Arm64", - "link": "https://download.visualstudio.microsoft.com/download/pr/d3aaa7cc-a603-4693-871b-53b1537a4319/5981099ca17a113b3ce1c080462c50ef/dotnet-sdk-3.1.416-linux-arm64.tar.gz", - "sha512": "0065c7afb129b1a0e0c11703309f3b45cf9a3c0ea156247f7cc61555f21c37054f215eb77add509dad77b1d388a4e6c585f8a8016109f31c5b64184b25e2c407" - }, - { - "name": "3.1", - "release": "3.1.415", - "architecture": "Arm64", - "link": "https://download.visualstudio.microsoft.com/download/pr/4a78a923-e891-40fe-88d2-4bff2c90519f/126bee4399caeabde4f34f4ace7f44e3/dotnet-sdk-3.1.415-linux-arm64.tar.gz", - "sha512": "7a5b9922988bcbde63d39f97e283ca1d373d5521cca0ab8946e2f86deaef6e21f00244228a0d5d8c38c2b9634b38bc7338b61984f0e12dd8fdb8b2e6eed5dd34" - }, - { - "name": "3.1", - "release": "3.1.414", - "architecture": "Arm64", - "link": "https://download.visualstudio.microsoft.com/download/pr/040f982b-bd08-496f-ae52-a60361a79546/7d572611a4177c48d868e0516ac192dc/dotnet-sdk-3.1.414-linux-arm64.tar.gz", - "sha512": "42b526d4ae914a0f1b04cbefe70b2c052eae9791dce54431ee5aff2e1bba5dbd08f49505a835319dab0551e9e9788f239e53ac154760cc8c0a85512cbe568408" - }, - { - "name": "3.1", - "release": "3.1.413", - "architecture": "Arm64", - "link": "https://download.visualstudio.microsoft.com/download/pr/dfd0ad22-3e47-432f-9aa1-f65b11a2ced2/d096c5d1561732c1658543fa8fb7a31f/dotnet-sdk-3.1.413-linux-arm64.tar.gz", - "sha512": "39f198f07577faf81f09ca621fb749d5aac38fc05e7e6bd6226009679abc7d001454068430ddb34b320901955f42de3951e2707e01bce825b5216df2bc0c8eca" - }, - { - "name": "3.1", - "release": "3.1.412", - "architecture": "Arm64", - "link": "https://download.visualstudio.microsoft.com/download/pr/f9f54199-f0b3-43ac-badd-f9ef6867641c/50bd985f26c59f5d63f29f571f7f89e5/dotnet-sdk-3.1.412-linux-arm64.tar.gz", - "sha512": "bfc6f58cb0b87b0a2cf42d91494b914ab0997c91289599cce1704ee33fac9773e5b37b48d40c2d0fc53709c7f94cc37c26533ef7d0eb90f6db05680b2b603da0" - }, - { - "name": "3.1", - "release": "3.1.411", - "architecture": "Arm64", - "link": "https://download.visualstudio.microsoft.com/download/pr/3dc91205-b4b2-4b90-bd5d-899b85c79454/6831ad89fe379c5ed01dbd362cbd4cac/dotnet-sdk-3.1.411-linux-arm64.tar.gz", - "sha512": "c9b03b858e6063fc200a103930fb5a3b03ae35b89b884bb461bad36ce34f14fa84c4e98edc7082bcf5b132fbd31d8d58192e62ae055e757d064ed98e4ec5c600" - }, - { - "name": "3.1", - "release": "3.1.410", - "architecture": "Arm64", - "link": "https://download.visualstudio.microsoft.com/download/pr/0d0ad29d-da90-42ce-a88d-94f47f9ddc09/bbfafc31b9a7e36140a74e0e157e9e3e/dotnet-sdk-3.1.410-linux-arm64.tar.gz", - "sha512": "58400007d55b122becb229a45d0fa8676890e16e818e229a9b6f5684e6d9133a22e4cb144f3a1af351c01fe9cc70056d43ab8164dca2b841cbdfae96fd7b02db" - }, - { - "name": "3.1", - "release": "3.1.409", - "architecture": "Arm64", - "link": "https://download.visualstudio.microsoft.com/download/pr/df1fe0a4-487f-4803-b541-4abb2f08ef2d/c905bb3ccc7f584963a45e32d7361d74/dotnet-sdk-3.1.409-linux-arm64.tar.gz", - "sha512": "edc011e5ee64fc76e8004aa73d439e7cea922ab00be6c70250c5f73cf6838b1935f5d3d3c9aa65f83bfd3923751bc1a6d92be3fba64a0a09a4acb4fd8d6db4c7" - }, - { - "name": "3.1", - "release": "3.1.408", - "architecture": "Arm64", - "link": "https://download.visualstudio.microsoft.com/download/pr/8aca4221-54b6-421d-9be0-f25e4b799463/b70cca6f2acddc5361f97dc7f77a8ddf/dotnet-sdk-3.1.408-linux-arm64.tar.gz", - "sha512": "5d69cb6d5f088452a6cc3a3635d5f6102e0217f3d5fa680287be9e67c211b0720fe493f05280ab4d2c1a905afe78e8ba9cb28151453172e7fcf10b0d9553e2d2" - }, - { - "name": "3.1", - "release": "3.1.407", - "architecture": "Arm64", - "link": "https://download.visualstudio.microsoft.com/download/pr/94a88a85-be1e-464a-8cdd-6eb23d8559a1/b715559dd50aec6097db76ca50e2154b/dotnet-sdk-3.1.407-linux-arm64.tar.gz", - "sha512": "4ae1c92bd4cdf0ea459591e87aea0e2f560df9d2c406e68dcf6667576ebf7761817683b565d42aeca74a6f03cdb8342d3fd1f9c81a4657a78043a5f765dc549c" - }, - { - "name": "3.1", - "release": "3.1.406", - "architecture": "Arm64", - "link": "https://download.visualstudio.microsoft.com/download/pr/fc9051ee-4071-4808-9e71-82d69328ab47/32ae2b7177d082fc52d89774e4f127fa/dotnet-sdk-3.1.406-linux-arm64.tar.gz", - "sha512": "e460ac35329e572dbf4005254129b9799c897f19261d01ea77a0aa196b9e0fecf804996b1157cea92731e30e08b5827ccb0c2d280ea9ab2b04b46492ed5e12a3" - }, - { - "name": "3.1", - "release": "3.1.405", - "architecture": "Arm64", - "link": "https://download.visualstudio.microsoft.com/download/pr/ebb398e7-06d1-48f9-94e7-ddae049b704f/6bb78627f0337b980ece2a3181963fbd/dotnet-sdk-3.1.405-linux-arm64.tar.gz", - "sha512": "e720b7c6497da52ce4a927b1a29df5c180fbf5b4f800185de61a31092c0bfa18a78bb9c2fee744fabc78214c5be6e9277f3dc14ae1426b55274f9726997fc733" - }, - { - "name": "3.1", - "release": "3.1.404", - "architecture": "Arm64", - "link": "https://download.visualstudio.microsoft.com/download/pr/de47cbe2-f75f-44c5-8250-7960a36d6591/76cfdbfb7bf17cce27378a9fddd969a6/dotnet-sdk-3.1.404-linux-arm64.tar.gz", - "sha512": "b22b60a06f4a1a409eb8eb8f5aec26454ff393bef9677294f0a9d61367caeb2a55fff1e4e282af5250365d27cee3b5cf7c31db8ff1c224f1c7225263b0e4a9aa" - }, - { - "name": "3.1", - "release": "3.1.403", - "architecture": "Arm64", - "link": "https://download.visualstudio.microsoft.com/download/pr/7a027d45-b442-4cc5-91e5-e5ea210ffc75/68c891aaae18468a25803ff7c105cf18/dotnet-sdk-3.1.403-linux-arm64.tar.gz", - "sha512": "c0f5070deca932d67a80b06268a69146ca403f1657e49d509513b5fe15cb5224cdfb5bec9cd2e63db6c83a48f3cb64d23d8cc30fcd98c620d9936544dbb6ce0b" - }, - { - "name": "3.1", - "release": "3.1.402", - "architecture": "Arm64", - "link": "https://download.visualstudio.microsoft.com/download/pr/186257d9-bca2-4dda-be74-006205965ec9/b2b63d45482701473d9731abc41ecc2a/dotnet-sdk-3.1.402-linux-arm64.tar.gz", - "sha512": "1704fb88a04cb5a00de5a22da4ad68237a7cce9aeaac608166f85082a4bfc86ba360b1d81c5ceb40c906cf7407e365aed73fa68a735d4ba90b334ab01a3a8455" - }, - { - "name": "3.1", - "release": "3.1.401", - "architecture": "Arm64", - "link": "https://download.visualstudio.microsoft.com/download/pr/8c39349a-23d0-46b0-8206-8b573a404709/b42fd441c1911acc90aaddaa58d7103f/dotnet-sdk-3.1.401-linux-arm64.tar.gz", - "sha512": "ab9afd226b920dce24bcd372cccb1965163829c26d1f11f1df3d8f9be5afb1d87f26b23e5fbeb58cf4ddaa040b8228fa00a786c379a454b9d99c2964197ab4cd" - }, - { - "name": "3.1", - "release": "3.1.302", - "architecture": "Arm64", - "link": "https://download.visualstudio.microsoft.com/download/pr/5ee48114-19bf-4a28-89b6-37cab15ec3f2/f5d1f54ca93ceb8be7d8e37029c8e0f2/dotnet-sdk-3.1.302-linux-arm64.tar.gz", - "sha512": "c2bebb673f217d9e7afb80b2b032c6f850d93e2419b5c0f9aa22676114a5c4fa91550a89b46757012fb9535405c1bb7902f0927f093769d4d055a8de84cfc5d8" - }, - { - "name": "3.1", - "release": "3.1.301", - "architecture": "Arm64", - "link": "https://download.visualstudio.microsoft.com/download/pr/fe5c0663-3ed1-4a93-95e1-fd068b89215b/14d1caad8fd2859d5f3514745a9bf6b3/dotnet-sdk-3.1.301-linux-arm64.tar.gz", - "sha512": "834dc5829730ea7abcf5adfca5557458d5de534597933dbdcec99abbd7eff00f3e1d0084b7f3572de80b4d333dee6d32cffa2d1ead022faad3957c95e5a920a0" - }, - { - "name": "3.1", - "release": "3.1.300", - "architecture": "Arm64", - "link": "https://download.visualstudio.microsoft.com/download/pr/e5e70860-a6d4-48cf-b0d1-eeba32657d80/2da3c605aaa65c7e4ac2ad0507a2e429/dotnet-sdk-3.1.300-linux-arm64.tar.gz", - "sha512": "b1d806dd719e61ae27297515d26e6ef12e615da131db4fd1c29b2acc4d6a68a6b0e4ce94ead4f8f737c203328d596422068c78495eba331a5759f595ed9ed149" - }, - { - "name": "3.1", - "release": "3.1.202", - "architecture": "Arm64", - "link": "https://download.visualstudio.microsoft.com/download/pr/15434752-2803-4722-b406-9a62d3e089d5/159bd7713f8a7ff82d7b2f756ae87f4f/dotnet-sdk-3.1.202-linux-arm64.tar.gz", - "sha512": "b570a423522de777d05de67410cea7d6d54461b7e2b82a18f4ca21093897836447f215b2737df909ee581508fbd1a61993f3ea1d9856b9a883caed3f7086f0a6" - }, - { - "name": "3.1", - "release": "3.1.201", - "architecture": "Arm64", - "link": "https://download.visualstudio.microsoft.com/download/pr/98a2e556-bedd-46c8-b3fa-96a9f1eb9556/09f60d50e3cbba0aa16d48ceec9dcb0b/dotnet-sdk-3.1.201-linux-arm64.tar.gz", - "sha512": "2f4f6b7ae55802b0beaf5d62bcb64f23ce132c9e08d8ce0f0af8d9b0b1b2c2629b1d4e805e83f831575c9968a86d1495dfa5292d2592af0cd1ae4a385daf42e7" - }, - { - "name": "3.1", - "release": "3.1.200", - "architecture": "Arm64", - "link": "https://download.visualstudio.microsoft.com/download/pr/781cb53b-046c-45fb-b18e-97ad65ff61a0/5c6ce7f4e031dad7cca0fdd5bcf4335b/dotnet-sdk-3.1.200-linux-arm64.tar.gz", - "sha512": "66ec47514d5bd5739db54c4469250e637f99e5792bca758783eb46722cab3a09920b02f7fe3873b1c430cd3ddea85075ce5e62e6f922a9be0c5f367ddf475b64" - }, - { - "name": "3.1", - "release": "3.1.120", - "architecture": "Arm64", - "link": "https://download.visualstudio.microsoft.com/download/pr/ead118ce-f9f9-4d0b-ac65-d90e330383a5/d673a7c4f7576e84bf778b5205b8a7ba/dotnet-sdk-3.1.120-linux-arm64.tar.gz", - "sha512": "507c5cade364a71b52c7c9c45ab4ee098b56ec24676ce960a092ba8497a10cacdd4d48184d24d88fd20c20698cf8bb9d0d409b02e706a7440351b37ebbc7de26" - }, - { - "name": "3.1", - "release": "3.1.119", - "architecture": "Arm64", - "link": "https://download.visualstudio.microsoft.com/download/pr/4b7053fb-c474-49b7-9064-64b5de935646/20148106c1aa427854d815112d844e8e/dotnet-sdk-3.1.119-linux-arm64.tar.gz", - "sha512": "943de9f6d4f46193cda08e4a149550a0490495ea19ff0a99119070b0d1d16a2c315ea0d05c04bdbfb742db993637ac9e264e7f1e00db4d99184d2874ec55e0ca" - }, - { - "name": "3.1", - "release": "3.1.118", - "architecture": "Arm64", - "link": "https://download.visualstudio.microsoft.com/download/pr/22ce33f1-b2b8-400f-b18f-7db499d8cb5d/448893643f3ec460e6cabc4e8053115f/dotnet-sdk-3.1.118-linux-arm64.tar.gz", - "sha512": "c79d4ca598caf3cc0f90e3b3b8b36bfc792b849f4cae538a6b1258c06f79b61d7c93c235e0fb80cae369a1b05a4a0132c7b45f3d862c1829fa356cbafb22e880" - }, - { - "name": "3.1", - "release": "3.1.117", - "architecture": "Arm64", - "link": "https://download.visualstudio.microsoft.com/download/pr/af36f11d-46b3-4adf-8d39-bd8607c58edb/2d2fe413f311e55cb729b86c49a214c2/dotnet-sdk-3.1.117-linux-arm64.tar.gz", - "sha512": "6c474b38dbaf6b55cb2f5ce27a98b50f3cb18c1fc1b15448785baf20c371fa42973eeafcf667ab42c7b840ddfde35a6b3fc819c06f9d76963617e74c34220c70" - }, - { - "name": "3.1", - "release": "3.1.116", - "architecture": "Arm64", - "link": "https://download.visualstudio.microsoft.com/download/pr/3279c2d9-9604-4c36-9c33-c7f23d16befa/a7593d4f3381a6679cda1864a8cfe0f4/dotnet-sdk-3.1.116-linux-arm64.tar.gz", - "sha512": "3a8f3744290899b860be9a2456df58d84101dbaf8c7383acc89a3c6daaaf03e5f708fe52d67f79bc7da5f3b0bfc4e0731add1ee59ed31b7e4efea40aa0d328d7" - }, - { - "name": "3.1", - "release": "3.1.115", - "architecture": "Arm64", - "link": "https://download.visualstudio.microsoft.com/download/pr/5ddeff0d-46d7-4688-ba7b-9b0b1eca68ff/2fcf0dde34749a46ec7dbd92eb255934/dotnet-sdk-3.1.115-linux-arm64.tar.gz", - "sha512": "325a8c43f74ab30b2a6ae52476f37853b361bc862b77eeb74007a3bdf44245615a0bbfaab0971fe6542b24d0a806aa1526869a62e5640635480c8110a024ae57" - }, - { - "name": "3.1", - "release": "3.1.114", - "architecture": "Arm64", - "link": "https://download.visualstudio.microsoft.com/download/pr/99d10374-6d9f-4f7d-b79c-c7de5530a082/17105b38223a2bdb340775162dc96e65/dotnet-sdk-3.1.114-linux-arm64.tar.gz", - "sha512": "822ce83c1fe1d5dd4cfbe7cde8cd68dc29db54ef673582e3c9e6423d252592317bb1a579d0f3598b4b967cd30ff015023400382f447a48ec1292649ba47cfadc" - }, - { - "name": "3.1", - "release": "3.1.113", - "architecture": "Arm64", - "link": "https://download.visualstudio.microsoft.com/download/pr/313e05b5-61c3-4644-b594-54ecc19c0544/81c3d5179e264f8adc28cc073cbacee2/dotnet-sdk-3.1.113-linux-arm64.tar.gz", - "sha512": "d25ddb029c5ca469895ec97b4d88f8b8ded157aeb51ff20106d53d428851ff8d146e3acd10e869e486f21cdea5d40aef2ba62d1a6586b8bbd4291179df9882b5" - }, - { - "name": "3.1", - "release": "3.1.112", - "architecture": "Arm64", - "link": "https://download.visualstudio.microsoft.com/download/pr/9b242a62-b203-44fe-9fb8-964f705b0c6d/ba9802acf2096e4832cb0aecfdc1519c/dotnet-sdk-3.1.112-linux-arm64.tar.gz", - "sha512": "0044bc449af04e0c6d349d0cb9c35a9f30e7e13bb906951e077911a49a5de88faede9f1f798625d10b6cdf8f35695f22e61623570b72f00324005b90d3b6c47d" - }, - { - "name": "3.1", - "release": "3.1.111", - "architecture": "Arm64", - "link": "https://download.visualstudio.microsoft.com/download/pr/3e6d3a57-eba8-4036-af2f-257cc4e7c5ff/5307347389772eb8a9b60bbee6a808fa/dotnet-sdk-3.1.111-linux-arm64.tar.gz", - "sha512": "0ea4d5a65d78ca09dcfd558e15accde34ca99647873ffd3584733e79d80ac4df493f466ce2434847bec4dddf2907e247522a5751fd05bd1eb3c82f6023f1e41f" - }, - { - "name": "3.1", - "release": "3.1.110", - "architecture": "Arm64", - "link": "https://download.visualstudio.microsoft.com/download/pr/af40aa01-90f9-47cb-8ed6-a186de459e6c/0cb13fba48e467e7788b1ccad83a5358/dotnet-sdk-3.1.110-linux-arm64.tar.gz", - "sha512": "ad20fb6d23ce9967cbd88390510c8207172854ea9ef5a3eae78cfd04dd73b6e312aaaa2f999ce81542c15021b4c313238e9ec01c1b7fb5f39b086848d39a244f" - }, - { - "name": "3.1", - "release": "3.1.109", - "architecture": "Arm64", - "link": "https://download.visualstudio.microsoft.com/download/pr/27fdcb48-766c-4f0e-8032-9f2afa40d3af/f2c6dac373046fa2d70e9a41b4a7dadf/dotnet-sdk-3.1.109-linux-arm64.tar.gz", - "sha512": "c3675edda58a8dc8ae9132fb1c5cea0b2a764d5c93ce2f94de0109676d4ef64f21f886d4354a10c441c522a2a8f13493c49501d62fc2c9e7a2f480eaff93cf56" - }, - { - "name": "3.1", - "release": "3.1.108", - "architecture": "Arm64", - "link": "https://download.visualstudio.microsoft.com/download/pr/572d9a06-dd1e-4349-a443-702b5382605c/dbe9647eceb84dea786af91cd11a9e3e/dotnet-sdk-3.1.108-linux-arm64.tar.gz", - "sha512": "b83513ffcfcddaca679edbd731ec4dbe8d653c46e626f0e1dcb700559a16effe0859d8c9a9b4a5e79e3de23521e0f37f8794e493075018c0fd7b8df7bc889d54" - }, - { - "name": "3.1", - "release": "3.1.107", - "architecture": "Arm64", - "link": "https://download.visualstudio.microsoft.com/download/pr/6e1897f2-8de1-41f2-828e-f2651d6448f9/c25e59f180ffb6a31d5e3f0a163391a0/dotnet-sdk-3.1.107-linux-arm64.tar.gz", - "sha512": "80f980455452b3d63c55585b8009a5475331202dc60c2d8b6990bf4ef367a652e07dd29d1e2d879042f65f90ecebadd4178042f3446186dfbdf530c8e39237a5" - }, - { - "name": "3.1", - "release": "3.1.106", - "architecture": "Arm64", - "link": "https://download.visualstudio.microsoft.com/download/pr/026fec12-345b-4c61-8731-1c0184594fc3/1738a6c43d2739dc6c2a9ec5caf0c41f/dotnet-sdk-3.1.106-linux-arm64.tar.gz", - "sha512": "aa2cb7ce94a013b1204dd9523e4fa0051dd43545b5a881647e44f1cf998dd1aafc7e1c709f603306c25bcf064ffbd8295cb17e17fb974e9dfa469e2ecf991f92" - }, - { - "name": "3.1", - "release": "3.1.105", - "architecture": "Arm64", - "link": "https://download.visualstudio.microsoft.com/download/pr/a2de5fc2-1c17-4ea2-bf3f-77c404e9263e/f9c79b6067afc819656ba9f6e6a4ed73/dotnet-sdk-3.1.105-linux-arm64.tar.gz", - "sha512": "10c13928f8f3b4a7bd0c782d3579e417b526f3497cb59deeef4ae9e7fae3228df2ecddfeb1c66f9c2d329204a62fa0c4bb759e2954a0e26ac00419b3efd6017e" - }, - { - "name": "3.1", - "release": "3.1.104", - "architecture": "Arm64", - "link": "https://download.visualstudio.microsoft.com/download/pr/7148dc66-dc70-4d6f-ae3d-c65aa8443259/734885b4dafa4097c0b264ca20be7e26/dotnet-sdk-3.1.104-linux-arm64.tar.gz", - "sha512": "a8bd5535af395dff4130d47e288837bda036b73a074f84755d1224d2c703304bcd36593dda5eb3f139a051060030a100516b5e8a2fd2fcdbb192eb04c1a341a8" - }, - { - "name": "3.1", - "release": "3.1.103", - "architecture": "Arm64", - "link": "https://download.visualstudio.microsoft.com/download/pr/2231ea64-1835-4e2f-bcff-f611bd803786/3e492dc64e6c3e6ca977e1e2abace10a/dotnet-sdk-3.1.103-linux-arm64.tar.gz", - "sha512": "f146fb6678262fa9214f02a0567a2bde2b2e56511fc9c2953b64aa4cfef11d9ef9a834324f21c64a497bdf347435db644eae999bc102c328bfbc1aaac062f24d" - }, - { - "name": "3.1", - "release": "3.1.102", - "architecture": "Arm64", - "link": "https://download.visualstudio.microsoft.com/download/pr/2ea7ea69-6110-4c39-a07c-bd4df663e49b/5d60f17a167a5696e63904f7a586d072/dotnet-sdk-3.1.102-linux-arm64.tar.gz", - "sha512": "7ad682935158599aeff1f04228df65ce99aa7d34fbe096422a55d6b0c6b38a7de4b9d0af1b667f63728b051d355f0c69526ffa90bb4c4d8ded53fb4ba63233c9" - }, - { - "name": "3.1", - "release": "3.1.101", - "architecture": "Arm64", - "link": "https://download.visualstudio.microsoft.com/download/pr/cf54dd72-eab1-4f5c-ac1e-55e2a9006739/d66fc7e2d4ee6c709834dd31db23b743/dotnet-sdk-3.1.101-linux-arm64.tar.gz", - "sha512": "03ea4cc342834a80f29b3b59ea1d7462e1814311dc6597bf2333359061b9b24f5ce98ed6ebf8d7ca05d42db31baba8ed8d4dec30a576fd818b3c0041c86d2937" - }, - { - "name": "3.1", - "release": "3.1.100", - "architecture": "Arm64", - "link": "https://download.visualstudio.microsoft.com/download/pr/5a4c8f96-1c73-401c-a6de-8e100403188a/0ce6ab39747e2508366d498f9c0a0669/dotnet-sdk-3.1.100-linux-arm64.tar.gz", - "sha512": "93634c555698ca5c3392332a93551b1548fa103328401c5c25e8955f085124b887b73736b70a139fc8eb8d622e47fcfc0aa25210b73a8f851906b32eaa8a9887" - }, - { - "name": "6.0", - "release": "6.0.403", - "architecture": "Arm32", - "link": "https://download.visualstudio.microsoft.com/download/pr/10cadabb-4cb4-4cca-94db-67cb31cb6f3a/5b3d102b4198da0a25ed12d83ae5633d/dotnet-sdk-6.0.403-linux-arm.tar.gz", - "sha512": "b07423700a92e3cc79f4e9e02c40e923352c09958e3307fd2ce7fc882509460c65a4404e8080f1b3852af98458512699ba43b37683916756666b4e2532cc8f46" - }, - { - "name": "6.0", - "release": "6.0.402", - "architecture": "Arm32", - "link": "https://download.visualstudio.microsoft.com/download/pr/7be1dda3-3412-4f9a-88f2-e6a8e5f118ff/7bd57a63288994da06e7a1b9a4e407e3/dotnet-sdk-6.0.402-linux-arm.tar.gz", - "sha512": "98b275af781ac7be20e22736d601ea667161640703b9d430340e517fb2c1bdcd6d06d5eb4f374cab1f6e29c9135005050ec89dd8dcf0ec97e7b0d9912e52f988" - }, - { - "name": "6.0", - "release": "6.0.401", - "architecture": "Arm32", - "link": "https://download.visualstudio.microsoft.com/download/pr/451f282f-dd26-4acd-9395-36cc3a9758e4/f5399d2ebced2ad9640db6283aa9d714/dotnet-sdk-6.0.401-linux-arm.tar.gz", - "sha512": "7d3c32f510a7298b8e4c32a95e7d3c9b0475d94510732a405163c7bff589ffda8964f2e9336d560bd1dc37461e6cb3da5809337a586da0288bdcc71496013ba0" - }, - { - "name": "6.0", - "release": "6.0.400", - "architecture": "Arm32", - "link": "https://download.visualstudio.microsoft.com/download/pr/5a24144e-0d7d-4cc9-b9d8-b4d32d6bb084/e882181e475e3c66f48a22fbfc7b19c0/dotnet-sdk-6.0.400-linux-arm.tar.gz", - "sha512": "a72aa70bfb15e21a20ddd90c2c3e37acb53e6f1e50f5b6948aac616b28f80ac81e1157e8db5688e21dc9a7496011ef0fcf06cdca74ddc7271f9a1c6268f4b1b2" - }, - { - "name": "6.0", - "release": "6.0.306", - "architecture": "Arm32", - "link": "https://download.visualstudio.microsoft.com/download/pr/a752d876-ddea-41a9-b8c6-5326b48791e3/ade6f4c1672c5357f140c44bba1b9acd/dotnet-sdk-6.0.306-linux-arm.tar.gz", - "sha512": "9ea0273b8e9a7ba0650aa6d989933d959a75f0c6c2cae46b1e6ba23913ddd25c6546275d9a21d540b760a9202fa802a484f713cacd0312405345142dfe452c6e" - }, - { - "name": "6.0", - "release": "6.0.305", - "architecture": "Arm32", - "link": "https://download.visualstudio.microsoft.com/download/pr/2f5729be-273f-45fd-a1ec-f91695951839/a26275ca2b4abf646c7783c5a409ff31/dotnet-sdk-6.0.305-linux-arm.tar.gz", - "sha512": "ae7ab7920e1ba2833ce55c44116e4400fdebd8ce48d3b193b590b938e25909a647a34706f32dd4fe2a8468ff61a8bc962d7c8dfde5404293b6254c5e980f46a6" - }, - { - "name": "6.0", - "release": "6.0.304", - "architecture": "Arm32", - "link": "https://download.visualstudio.microsoft.com/download/pr/32139893-1030-4515-a6a6-e3392685e9af/5f4b8acdaabe97f6d6d105e38a5c1557/dotnet-sdk-6.0.304-linux-arm.tar.gz", - "sha512": "4cbcb1fe9d9c3be643d3d0247e5cb4eff95778429a4eeae38060d268b3d31ef4c51cd47357c03a4d4281c5eb20feaa07ac140d430ffe4270c0f37aba3af546b4" - }, - { - "name": "6.0", - "release": "6.0.303", - "architecture": "Arm32", - "link": "https://download.visualstudio.microsoft.com/download/pr/89e0bd36-57eb-41e4-95c9-4e35569e0f6b/1e7b6862caa6488465ab74d7c44bf130/dotnet-sdk-6.0.303-linux-arm.tar.gz", - "sha512": "444b8055f6814d5282e320ae243244fa27fe8afe76356c7d06e03a399ae2d41806cefc523a7bf12f36931931da88964d6797f1758a9cf23671aae318f41d3167" - }, - { - "name": "6.0", - "release": "6.0.302", - "architecture": "Arm32", - "link": "https://download.visualstudio.microsoft.com/download/pr/f35557f7-d5e1-43be-93b6-e59ee8bd823c/9dd2c25a4d7351412ff5902c355732ca/dotnet-sdk-6.0.302-linux-arm.tar.gz", - "sha512": "0d31c7e8ccf02c8dea92d7b60bcb15e15912d74e7ee2ab8fd88ee03c4fbd8f292c356357d08ec23c2aedc5e3e0803d42ce16f3fff36245739d0cac6634bc3387" - }, - { - "name": "6.0", - "release": "6.0.301", - "architecture": "Arm32", - "link": "https://download.visualstudio.microsoft.com/download/pr/a218e3b9-941b-43be-bfb1-615862777457/80954de34ab68729981ed372a8d25b46/dotnet-sdk-6.0.301-linux-arm.tar.gz", - "sha512": "ef7d028b80eaaae18b71195e89e00dea2186d455f7b72f373fc0a57074e8320c8e9245167c06e30a2ddade4ab21ad5e8b05d04a6ea11c1de68b7c9a6f9807d25" - }, - { - "name": "6.0", - "release": "6.0.300", - "architecture": "Arm32", - "link": "https://download.visualstudio.microsoft.com/download/pr/ef1d4f88-cbe9-47c2-9fb3-e7e772be1a4c/4fbbf2a11cee4f52a478a46b1a69ed03/dotnet-sdk-6.0.300-linux-arm.tar.gz", - "sha512": "362e09bbe36a827beacbf36af6d66f7a6eb6da92e002e9a466a597f2fc181754e8893840c68c67a6c5e94b39e2dec1da360c72814bd904b325171ff7d06c56eb" - }, - { - "name": "6.0", - "release": "6.0.203", - "architecture": "Arm32", - "link": "https://download.visualstudio.microsoft.com/download/pr/88fefcd9-70aa-4bca-80f8-4a78d6b8b53c/7ee6202e44cea4fa104f5f788425fcaf/dotnet-sdk-6.0.203-linux-arm.tar.gz", - "sha512": "e6cb656e4acf450023413cd0e155e9c4f294887dec1fb6c87d4e9951eb29d0ad697f50719e42fe3dbea0a34f9d297dce9261c541a7e43e6e4a359c8db215390d" - }, - { - "name": "6.0", - "release": "6.0.202", - "architecture": "Arm32", - "link": "https://download.visualstudio.microsoft.com/download/pr/e41a177d-9f0b-4afe-97a4-53587cd89d00/c2c897aa6442d49c1d2d86abb23c20b2/dotnet-sdk-6.0.202-linux-arm.tar.gz", - "sha512": "8c2d56256f4bebe58caee7810b7689408ff023b1f2e68f99fa375f0115db41ef0c3eb160b9ab84dc2764443a045801a4b03f6bc9090e0c1583fca2587ea0d9d6" - }, - { - "name": "6.0", - "release": "6.0.201", - "architecture": "Arm32", - "link": "https://download.visualstudio.microsoft.com/download/pr/eefec3fa-c4c3-454d-bd7d-8fda31d15e5f/62668641ffc94db5fa11187f14a981f8/dotnet-sdk-6.0.201-linux-arm.tar.gz", - "sha512": "5a683430325a90dd1d8e0071a1868939fb01268f9eb389ca1dc40956fde6b9f45bec086553ad3333139e530dfe5afae48195bcdfec388b0b568989924a1f1dd7" - }, - { - "name": "6.0", - "release": "6.0.200", - "architecture": "Arm32", - "link": "https://download.visualstudio.microsoft.com/download/pr/4cfcfa53-f421-4257-8cd2-d4078f9ffe90/008804a5475fa0d46b9e8f03cb78bfcd/dotnet-sdk-6.0.200-linux-arm.tar.gz", - "sha512": "c2950d5da671a50c955d07997b288c02076b1d69014d3ac3f5941179e29a67b4d56fc5acbde85fe13ffd46efa95ead05b39d90dc577d2a668f637e6a9547944a" - }, - { - "name": "6.0", - "release": "6.0.111", - "architecture": "Arm32", - "link": "https://download.visualstudio.microsoft.com/download/pr/f8693beb-cb52-4d0b-9362-c93f47f2e0f5/7fa339566925e191039a6db91de3d547/dotnet-sdk-6.0.111-linux-arm.tar.gz", - "sha512": "44388611a84620e09e3adea7ada8ce4e04ef675d54c3a6ef12017bbed25e7c5d100089a700c503e9edc8605f9c0f48995f3b4595bd3179910a6cf4e2c11b1a53" - }, - { - "name": "6.0", - "release": "6.0.110", - "architecture": "Arm32", - "link": "https://download.visualstudio.microsoft.com/download/pr/ec48c241-aa98-44fb-8e9e-f8ec91f048ec/8c9b79c23058a1c0ad7b3e0fdc340d41/dotnet-sdk-6.0.110-linux-arm.tar.gz", - "sha512": "ed8a0a3c5edc42b6e82dc925c35ebd3ba0ddf94b98c97febb0f4ffa758b369b2c063935fa62cc5e18c1f26e64e0618daf2600d8a9946a0d44f6103d725d99091" - }, - { - "name": "6.0", - "release": "6.0.109", - "architecture": "Arm32", - "link": "https://download.visualstudio.microsoft.com/download/pr/743cf42d-f201-4dd6-ada8-37bd46cef977/d3508390013604f9023dab6698492aea/dotnet-sdk-6.0.109-linux-arm.tar.gz", - "sha512": "b757ffdef4b87a9394ce0a367f5563b2900e9720eba6b071ce22e454eaddcb7983bde182d90ef552b5b903c2b5505af5ab642190c982dfc18649ea8ee8657886" - }, - { - "name": "6.0", - "release": "6.0.108", - "architecture": "Arm32", - "link": "https://download.visualstudio.microsoft.com/download/pr/69439f13-034e-4961-b0ba-1ef3b7dfeada/b3afee58e324bcd2c0e2a124b7472635/dotnet-sdk-6.0.108-linux-arm.tar.gz", - "sha512": "c3726e053b447e7baec21e015a91dddb884a79a483c7d2a247f2fe143a5cff25e5d7e822944c6b12f0b030d39b4e71c1da437e2ee4c094dd1bcdeccad3c44568" - }, - { - "name": "6.0", - "release": "6.0.107", - "architecture": "Arm32", - "link": "https://download.visualstudio.microsoft.com/download/pr/e69db7cd-ea5c-40ea-87cd-cc567cbd6c3f/0765ead789975a5f4359fe44e3e7596a/dotnet-sdk-6.0.107-linux-arm.tar.gz", - "sha512": "05364e5f3c3be3f66420310c1fe26550d639c6e95b43ed004ea79017ee8c973d73349ff87c5eca4c560f80fea55a435c1c22f3889d5c015b85474533395245f4" - }, - { - "name": "6.0", - "release": "6.0.106", - "architecture": "Arm32", - "link": "https://download.visualstudio.microsoft.com/download/pr/cba88c45-5ea4-43ed-b495-099d9925b561/af8f6468186950a4f87e932f888434a8/dotnet-sdk-6.0.106-linux-arm.tar.gz", - "sha512": "9973fa26f7129e4f148e65123512ba02644b78b47a7d4b1fbeeafd09038beac66664b285ace5e228a7818f0c35a357d31ff1d580795182c872c514fdb3b90e35" - }, - { - "name": "6.0", - "release": "6.0.105", - "architecture": "Arm32", - "link": "https://download.visualstudio.microsoft.com/download/pr/e3c5dfe0-e6ba-4660-a73e-d6edacfdc894/4fba777299a959a5e3125a955866c49a/dotnet-sdk-6.0.105-linux-arm.tar.gz", - "sha512": "edfb33b0956cf1398f460847505cebd8571b82ae870f64e48de54b67d5cbdb5d3cdcfe2328ce9f096d665751bae844eb5a1f66fb707da2ad888d694016444adb" - }, - { - "name": "6.0", - "release": "6.0.104", - "architecture": "Arm32", - "link": "https://download.visualstudio.microsoft.com/download/pr/4a905b9c-e97e-4ab6-a258-d9dd9d41564f/af30654ab6af793527e9652dfa29c817/dotnet-sdk-6.0.104-linux-arm.tar.gz", - "sha512": "96c3af8b9c920b542a4e9fc3ce26242a0d193404d74b7e88d223d44b2e597da83d44892d89f7fed5385acad1ff5f787bbd81381f01c3cd1b0d89888d14688750" - }, - { - "name": "6.0", - "release": "6.0.103", - "architecture": "Arm32", - "link": "https://download.visualstudio.microsoft.com/download/pr/3324d5ae-3b19-4906-a5be-c088c08d4bae/b95253ada2fc9ad129c58a66db09f3ab/dotnet-sdk-6.0.103-linux-arm.tar.gz", - "sha512": "e1ad50651dbd80cd9a089a5032f32c110b47b74935813aa3cbd8a8473ef65f139e5bed61f4952c9ff4ff95fff11c51a12095dd33e228733208222c13c95034c5" - }, - { - "name": "6.0", - "release": "6.0.102", - "architecture": "Arm32", - "link": "https://download.visualstudio.microsoft.com/download/pr/2509d293-6a9f-4108-8fe1-10e78341c5df/18f693729320bdbb5e8d936460dd0e2b/dotnet-sdk-6.0.102-linux-arm.tar.gz", - "sha512": "a72a0e81c62478b0dc662ef0aaeb7f96e7dd534e90b3ac1bdab1ca98dd93a4605881dba6e9ed2315781fdf71f5b33acb1aa5e28090c7a1693405bebed5853094" - }, - { - "name": "6.0", - "release": "6.0.101", - "architecture": "Arm32", - "link": "https://download.visualstudio.microsoft.com/download/pr/72888385-910d-4ef3-bae2-c08c28e42af0/59be90572fdcc10766f1baf5ac39529a/dotnet-sdk-6.0.101-linux-arm.tar.gz", - "sha512": "f9e212dc4cccbe665d9aac23da6bdddce4957ae4e4d407cf3f1d6da7e79784ebd408c3a59b3ecc6ceaa930b37cf01a4a91c6b38517970d49227e96e50658cc46" - }, - { - "name": "6.0", - "release": "6.0.100", - "architecture": "Arm32", - "link": "https://download.visualstudio.microsoft.com/download/pr/1f85b038-9917-4d0a-8485-5dc86510eec7/a7555924fe292c6c2140893f066abe65/dotnet-sdk-6.0.100-linux-arm.tar.gz", - "sha512": "c1e555893c48c4f4256d3e6b1d36b31d8a4d7763a6e958fb63dd31436c660648d481612b5e25d79a613e84a1954f5eac2c9c2b740bf410958172780f7bbeaeb3" - }, - { - "name": "6.0", - "release": "6.0.403", - "architecture": "Arm64", - "link": "https://download.visualstudio.microsoft.com/download/pr/67ca3f83-3769-4cd8-882a-27ab0c191784/bf631a0229827de92f5c026055218cc0/dotnet-sdk-6.0.403-linux-arm64.tar.gz", - "sha512": "fe62f6eca80acb6774f0a80c472dd02851d88f7ec09cc7f1cadd9981ec0ee1ceb87224911fc0c544cb932c7f5a91c66471a0458b50f85c899154bc8c3605a88e" - }, - { - "name": "6.0", - "release": "6.0.402", - "architecture": "Arm64", - "link": "https://download.visualstudio.microsoft.com/download/pr/234daf6a-5e12-4fa3-a73b-b12db44a3154/df7c012e5e4e2cc510de9226425dad88/dotnet-sdk-6.0.402-linux-arm64.tar.gz", - "sha512": "2f5351192e87c2dd196d975e7618bd7b0b542034d0b1bc932fe944d8cbabb0ed2599e98e88d9757e68f198559961ab3350d8eddfacc2951df00fbf6a7e44f244" - }, - { - "name": "6.0", - "release": "6.0.401", - "architecture": "Arm64", - "link": "https://download.visualstudio.microsoft.com/download/pr/a567a07f-af9d-451a-834c-a746ac299e6b/1d9d74b54cf580f93cad71a6bf7b32be/dotnet-sdk-6.0.401-linux-arm64.tar.gz", - "sha512": "8c05f9e02e0a48fcc3e4534fa7225fe5b974c07f1a4788c46207e18e94031194e1c881e40452ee6c432764e92331c50ae47305d4aec5afa363fab3a8a6727cdf" - }, - { - "name": "6.0", - "release": "6.0.400", - "architecture": "Arm64", - "link": "https://download.visualstudio.microsoft.com/download/pr/901f7928-5479-4d32-a9e5-ba66162ca0e4/d00b935ec4dc79a27f5bde00712ed3d7/dotnet-sdk-6.0.400-linux-arm64.tar.gz", - "sha512": "a21010f9e0e091bf0a4df9dfc4ec9893c056c2b07b10be093ea392a4fa5c8a38bad9535f66e570b45dc25165b685199fb729434b845bcfb35f8b79cceb22c632" - }, - { - "name": "6.0", - "release": "6.0.306", - "architecture": "Arm64", - "link": "https://download.visualstudio.microsoft.com/download/pr/fb258146-ac1b-480d-99b2-fecb7a607705/66e827540def06412bf11da9e6accb61/dotnet-sdk-6.0.306-linux-arm64.tar.gz", - "sha512": "cc49223db9c6309c4f0f7e208dfa07a1e9bdde032c2047b9aa2711e03d8aa0da91da22d728b332c2c9adbc54f423bb7e1dcdcb3888ed9ceef899d6e74bd308b2" - }, - { - "name": "6.0", - "release": "6.0.305", - "architecture": "Arm64", - "link": "https://download.visualstudio.microsoft.com/download/pr/031e2cdb-71ae-4f10-8862-e2883ff82f03/fdbd377c5d84c1c07c4b8d1c87cca6bc/dotnet-sdk-6.0.305-linux-arm64.tar.gz", - "sha512": "0d7eb35dc1b2ee8c31710bab2eeb1f6871819168bcdf7aff257074cd0c4eafc3ad52875a45cec38ad799a12e8c81bddc6267f6b608aadfd9607d435cb3e6ba6f" - }, - { - "name": "6.0", - "release": "6.0.304", - "architecture": "Arm64", - "link": "https://download.visualstudio.microsoft.com/download/pr/6d879cce-4273-42d6-b39d-0cc4d3ab198e/3a63185d3aa4685be69aa395a0986f24/dotnet-sdk-6.0.304-linux-arm64.tar.gz", - "sha512": "69dbd86331002990d7f6c915b0fb832c5d8ba55bb4dacbd6a4065ca7d59e92902fb5052f6cd905a453e42355a655d526b5c12be8fb5d12255af2d345d5b12846" - }, - { - "name": "6.0", - "release": "6.0.303", - "architecture": "Arm64", - "link": "https://download.visualstudio.microsoft.com/download/pr/625d6cbb-7d21-41ab-a474-3ba603770d1d/7016fc39dc1eb5c8c7c5e2cc36440212/dotnet-sdk-6.0.303-linux-arm64.tar.gz", - "sha512": "b6eed880882873a35fdda99f8cc4a3d7b72851004244cd9a2e8656b475c4e766da78cfb33e0f034e742da39200b583ae9970c284e1401f1a4645c9d9d4429282" - }, - { - "name": "6.0", - "release": "6.0.302", - "architecture": "Arm64", - "link": "https://download.visualstudio.microsoft.com/download/pr/33389348-a7d7-41ae-850f-ec46d3ca9612/36bad11f948b05a4fa9faac93c35e574/dotnet-sdk-6.0.302-linux-arm64.tar.gz", - "sha512": "26e98a63665d707b1a7729f1794077316f9927edd88d12d82d0357fe597096b0d89b64a085fcdf0cf49807a443bbfebb48e10ea91cea890846cf4308e67c4ea5" - }, - { - "name": "6.0", - "release": "6.0.301", - "architecture": "Arm64", - "link": "https://download.visualstudio.microsoft.com/download/pr/06c4ee8e-bf2c-4e46-ab1c-e14dd72311c1/f7bc6c9677eaccadd1d0e76c55d361ea/dotnet-sdk-6.0.301-linux-arm64.tar.gz", - "sha512": "978dd04f78ac3d6b594c47f1482bba0abe93f0b37379c1c46a2b9b33bdf5188576b055250546295de39bb22cba93ea9b31c31bb026a319ad1b3fc507db44481f" - }, - { - "name": "6.0", - "release": "6.0.300", - "architecture": "Arm64", - "link": "https://download.visualstudio.microsoft.com/download/pr/7c62b503-4ede-4ff2-bc38-50f250a86d89/3b5e9db04cbe0169e852cb050a0dffce/dotnet-sdk-6.0.300-linux-arm64.tar.gz", - "sha512": "67eb088ccad197a39f104af60f3e6d12ea9b17560e059c0f7c8e956005d919d00bf0f3e487b06280be63ad57aa8895f16ebc8c92107c5019c9cf47bd620ea925" - }, - { - "name": "6.0", - "release": "6.0.203", - "architecture": "Arm64", - "link": "https://download.visualstudio.microsoft.com/download/pr/c7d9ebd1-16cd-44d5-b406-177ad676c630/1a46912f74a71117bee6077ad3b832df/dotnet-sdk-6.0.203-linux-arm64.tar.gz", - "sha512": "552448b13cc77fc1e6189f4954eab16e8a87822189f3e488e6bc73ac099a8fbe6f34b667dabee637522e35edad30661ba32cdd4b94d57bb5cea9441e2f4abf19" - }, - { - "name": "6.0", - "release": "6.0.202", - "architecture": "Arm64", - "link": "https://download.visualstudio.microsoft.com/download/pr/952f5525-7227-496f-85e5-09cadfb44629/eefd0f6eb8f809bfaf4f0661809ed826/dotnet-sdk-6.0.202-linux-arm64.tar.gz", - "sha512": "2d0021bb4cd221ffba6888dbd6300e459f45f4f9d3cf7323f3b97ee0f093ef678f5a36d1c982296f4e15bbcbd7275ced72c3e9b2fc754039ba663d0612ffd866" - }, - { - "name": "6.0", - "release": "6.0.201", - "architecture": "Arm64", - "link": "https://download.visualstudio.microsoft.com/download/pr/33c6e1e3-e81f-44e8-9de8-91934fba3c94/9105f95a9e37cda6bd0c33651be2b90a/dotnet-sdk-6.0.201-linux-arm64.tar.gz", - "sha512": "2ea443c27ab7ca9d566e4df0e842063642394fd22fe2a8620371171c8207ae6a4a72c8c54fc6af5b6b053be25cf9c09a74504f08b963e5bd84544619aed9afc2" - }, - { - "name": "6.0", - "release": "6.0.200", - "architecture": "Arm64", - "link": "https://download.visualstudio.microsoft.com/download/pr/ad60a07c-d4f0-4225-9154-c3a2ec0f34cf/a588cd2b94db2214f6e5dcd02c4aa37a/dotnet-sdk-6.0.200-linux-arm64.tar.gz", - "sha512": "4e2b8f65f4cd9d1e54233b377e7efef5202eee3fa5e4592131ff03ecab032bfccdc91f4e8c806064fb769c48f946634bf1e420afecd7fcd2d981d40eeec5a881" - }, - { - "name": "6.0", - "release": "6.0.111", - "architecture": "Arm64", - "link": "https://download.visualstudio.microsoft.com/download/pr/f83d8e86-8cc1-4787-b6a7-b318d19d9582/9eca68b2392ba2798b70d598d49837ec/dotnet-sdk-6.0.111-linux-arm64.tar.gz", - "sha512": "7473eef3715c83ff399adaaf2605ae28f0144186024e3a4a5661ca978cc376262203147d77c51476cd88b5d7bdd7b3700221f214083eb77838a41a1357766b7c" - }, - { - "name": "6.0", - "release": "6.0.110", - "architecture": "Arm64", - "link": "https://download.visualstudio.microsoft.com/download/pr/1a0faccb-a85e-43b7-b2a6-78ee8b975855/572b91765e7db29d33887cf4b87bc86c/dotnet-sdk-6.0.110-linux-arm64.tar.gz", - "sha512": "da312773a6def29612ea6898c489b86b2546e5e52c7c890134712c64fb3c0e52dfab88d8589858f9a9b39c3d2c9fc39406d6da251e3cfca399eb93df0c9ad5c6" - }, - { - "name": "6.0", - "release": "6.0.109", - "architecture": "Arm64", - "link": "https://download.visualstudio.microsoft.com/download/pr/5da20803-1c48-4e54-a87c-88cda79efd77/4e6cd608eca3e34540d89e1ec3df5e6b/dotnet-sdk-6.0.109-linux-arm64.tar.gz", - "sha512": "eb4a42a45cbf8a92d2f78d4299ec34ab11d6d8dbc28dcab9dbe95648e40895a0dc56675a99b4df9e1f5a42ab0f3368322ae17381810d9bac5cb8623066120b80" - }, - { - "name": "6.0", - "release": "6.0.108", - "architecture": "Arm64", - "link": "https://download.visualstudio.microsoft.com/download/pr/f3162067-d239-4518-9423-32559f4b6ebe/e944b933ee94602074678b2ea1374b6e/dotnet-sdk-6.0.108-linux-arm64.tar.gz", - "sha512": "95f5c47fb27882f5554b0ee0befb813357875d6bfd19447c7c902ce225ed394bbb6cd945c5f7d5233fe0d3a16a1c006f47eba9d6be51cf8578fc5fbeba2806f0" - }, - { - "name": "6.0", - "release": "6.0.107", - "architecture": "Arm64", - "link": "https://download.visualstudio.microsoft.com/download/pr/dd4f49ba-39b0-4358-bd82-18cb5b1350d1/ec891910fd6f13c22448a8162fe1f017/dotnet-sdk-6.0.107-linux-arm64.tar.gz", - "sha512": "946b43f0da3e5d94d20163d6a94fc4fcef66c1c18d6fa02530e769201d96c9e2125c0f35532ea87e0c5b4f3a491daa6d4b8d6dd23f4eb9da32625ca0a4065e14" - }, - { - "name": "6.0", - "release": "6.0.106", - "architecture": "Arm64", - "link": "https://download.visualstudio.microsoft.com/download/pr/3a8c5d20-21ac-40cf-b3c5-2efd29a88870/b6f62cff4a13924832a81574e5ef5a80/dotnet-sdk-6.0.106-linux-arm64.tar.gz", - "sha512": "86fabee178d16dcf9b5ff3d2f786c72be6bea5838b69c9e02d3a356703799378094a87f3ae4cc3ed6564f2c762a1837c4d8f91a228ba94cf58546d5ba7363f8f" - }, - { - "name": "6.0", - "release": "6.0.105", - "architecture": "Arm64", - "link": "https://download.visualstudio.microsoft.com/download/pr/54abc3c4-4ed6-4d0e-804f-71372f91051e/6dc0cc5cbf2052e6ace42248473464f3/dotnet-sdk-6.0.105-linux-arm64.tar.gz", - "sha512": "e995bf3da74b2b9b0cb17339be5cd34299e2d5471cd72e923919e9ac22badde369d23357f09c3dbc121c10da366f6650ee8721adec3a11911ef5fed223b9accd" - }, - { - "name": "6.0", - "release": "6.0.104", - "architecture": "Arm64", - "link": "https://download.visualstudio.microsoft.com/download/pr/e61cf583-1e44-4ac5-a04f-5b59fda42ea7/df3853bb318af131f7eafa61f2b839b8/dotnet-sdk-6.0.104-linux-arm64.tar.gz", - "sha512": "91fa1114a656173a988aafd65c657c9498c34ef9145eac60b6feacc8a08f68538defeb38af472e2626ffd0669eb62140fdb1408771db0e2b63501baf2a646f29" - }, - { - "name": "6.0", - "release": "6.0.103", - "architecture": "Arm64", - "link": "https://download.visualstudio.microsoft.com/download/pr/84b9132d-f18e-4f54-bd34-ed4ffcbfa1f7/8a4cc6ca1d60a58963a1866a2b1857fe/dotnet-sdk-6.0.103-linux-arm64.tar.gz", - "sha512": "e9efdbbb36a064b2cddcadc7b8c3a92dabc0da5c9491a6a39580720739d9a7517fee8e9ea2073d2dbfc91685f093a950bcb80713ba77413da07c17cddbbbfe55" - }, - { - "name": "6.0", - "release": "6.0.102", - "architecture": "Arm64", - "link": "https://download.visualstudio.microsoft.com/download/pr/93dd8d1e-f2af-45b1-8e86-9b8c3d58f4d2/b3fc3ef9da1db691043387fcb56f4d6f/dotnet-sdk-6.0.102-linux-arm64.tar.gz", - "sha512": "790cbf322ca8fed32eaf574f19d0bdc05656c5a88a65aa4dba8269cfce1443cd7cdeecdd3a40e353c368f055490b70592ca7f15f981a66c5b3a9517d0b09e4cb" - }, - { - "name": "6.0", - "release": "6.0.101", - "architecture": "Arm64", - "link": "https://download.visualstudio.microsoft.com/download/pr/d43345e2-f0d7-4866-b56e-419071f30ebe/68debcece0276e9b25a65ec5798cf07b/dotnet-sdk-6.0.101-linux-arm64.tar.gz", - "sha512": "04cd89279f412ae6b11170d1724c6ac42bb5d4fae8352020a1f28511086dd6d6af2106dd48ebe3b39d312a21ee8925115de51979687a9161819a3a29e270a954" - }, - { - "name": "6.0", - "release": "6.0.100", - "architecture": "Arm64", - "link": "https://download.visualstudio.microsoft.com/download/pr/adcd9310-5072-4179-9b8b-16563b897995/15a7595966f488c74909e4a9273c0e24/dotnet-sdk-6.0.100-linux-arm64.tar.gz", - "sha512": "e5983c1c599d6dc7c3c7496b9698e47c68247f04a5d0d1e3162969d071471297bce1c2fd3a1f9fb88645006c327ae79f880dcbdd8eefc9166fd717331f2716e7" - }, - { - "name": "7.0", - "release": "7.0.100", - "architecture": "Arm32", - "link": "https://download.visualstudio.microsoft.com/download/pr/fd193b5b-f171-436e-8783-5eb77a7ad5ee/2a0af2a874f3c4f7da0199dc64996829/dotnet-sdk-7.0.100-linux-arm.tar.gz", - "sha512": "11c1150357a0a79095b563671bc038085f8bbbc678a47681c4decade22fcb18504e60732518e681a5688008e7ffbad69933a8ff3bd91c09ff4df66a80a596809" - }, - { - "name": "7.0", - "release": "7.0.100", - "architecture": "Arm64", - "link": "https://download.visualstudio.microsoft.com/download/pr/47337472-c910-4815-9d9b-80e1a30fcf16/14847f6a51a6a7e53a859d4a17edc311/dotnet-sdk-7.0.100-linux-arm64.tar.gz", - "sha512": "0a332df58891e808c9adc2b785e9b0e658b29b494963c8d501b0f8806ff5d3daad4614886349cbba86af638ed7ac76e78a2d05aeca13bac25d5f45fbe62b8251" - } - ] -} \ No newline at end of file diff --git a/RaspberryDebugger/source.extension.vsixmanifest b/RaspberryDebugger/source.extension.vsixmanifest index 1763b08..e5c754e 100644 --- a/RaspberryDebugger/source.extension.vsixmanifest +++ b/RaspberryDebugger/source.extension.vsixmanifest @@ -20,6 +20,9 @@ Raspberry Debugger forked from nforgeio/RaspberryDebugger amd64 + + amd64 + diff --git a/global.json b/global.json new file mode 100644 index 0000000..f02cc0e --- /dev/null +++ b/global.json @@ -0,0 +1,7 @@ +{ + "sdk": { + "version": "8.0.0", + "rollForward": "latestFeature", + "allowPrerelease": false + } +} \ No newline at end of file diff --git a/version.json b/version.json new file mode 100644 index 0000000..055c1df --- /dev/null +++ b/version.json @@ -0,0 +1,13 @@ +{ + "$schema": "https://raw.githubusercontent.com/dotnet/Nerdbank.GitVersioning/master/src/NerdBank.GitVersioning/version.schema.json", + "version": "3.3-beta", + "publicReleaseRefSpec": [ + "^refs/heads/main$", + "^refs/heads/v\\d+(?:\\.\\d+)?$" + ], + "cloudBuild": { + "buildNumber": { + "enabled": true + } + } +} \ No newline at end of file