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
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