diff --git a/RaspberryDebugger/Commands/DebugStartCommand.cs b/RaspberryDebugger/Commands/DebugStartCommand.cs index 476c388..ae71e63 100644 --- a/RaspberryDebugger/Commands/DebugStartCommand.cs +++ b/RaspberryDebugger/Commands/DebugStartCommand.cs @@ -205,7 +205,8 @@ await NeonHelper.WaitForAsync(async () => if (!launchReady) return; - OpenWebBrowser(projectProperties, foundWebServer, connection); + await Task.Delay(3000); + OpenWebBrowser(projectProperties, projectSettings, foundWebServer, connection); } } @@ -213,16 +214,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/Connection/Connection.cs b/RaspberryDebugger/Connection/Connection.cs index aef2060..92fe8f6 100644 --- a/RaspberryDebugger/Connection/Connection.cs +++ b/RaspberryDebugger/Connection/Connection.cs @@ -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; } } @@ -310,83 +310,91 @@ await PackageHelper.ExecuteWithProgressAsync( 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 - - # 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 - "; + $""" + # 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={PackageHelper.RemoteDebuggerFolder} + + # Get the chip architecture + uname -m + + # 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"); @@ -398,6 +406,7 @@ chmod 755 /lib/dotnet var processor = 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(); @@ -408,6 +417,7 @@ chmod 755 /lib/dotnet Log($"[{Name}]: processor: {processor}"); Log($"[{Name}]: path: {path}"); Log($"[{Name}]: unzip: {hasUnzip}"); + Log($"[{Name}]: lsof: {hasLsof}"); Log($"[{Name}]: debugger: {hasDebugger}"); Log($"[{Name}]: sdks: {sdkLine}"); Log($"[{Name}]: model: {model}"); @@ -449,6 +459,7 @@ chmod 755 /lib/dotnet processor: processor, path: path, hasUnzip: hasUnzip, + hasLsof: hasLsof, hasDebugger: hasDebugger, installedSdks: sdks, model: model, @@ -480,19 +491,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 +532,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))); } @@ -579,30 +590,30 @@ private async Task DownloadSdkAsync(SdkCatalogItem targetSdk) 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 + + 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 + """; try { @@ -644,37 +655,37 @@ private async Task InstallSdkAsync(SdkCatalogItem targetSdk) 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 - "; + $""" + 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 { @@ -750,13 +761,13 @@ public async Task SetupDebuggerAsync() async () => { var installScript = - $@" - if ! curl -sSL https://aka.ms/getvsdbgsh | /bin/sh /dev/stdin -v latest -l {PackageHelper.RemoteDebuggerFolder} ; then - exit 1 - fi + $""" + if ! curl -sSL https://aka.ms/getvsdbgsh | /bin/sh /dev/stdin -v latest -l {PackageHelper.RemoteDebuggerFolder} ; then + exit 1 + fi - exit 0 - "; + exit 0 + """; try { @@ -783,6 +794,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. diff --git a/RaspberryDebugger/Connection/Status.cs b/RaspberryDebugger/Connection/Status.cs index 4e8e834..4e35a4f 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,6 +44,7 @@ public Status( string processor, string path, bool hasUnzip, + bool hasLsof, bool hasDebugger, IEnumerable installedSdks, string model, @@ -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. diff --git a/RaspberryDebugger/DebugHelper.cs b/RaspberryDebugger/DebugHelper.cs index c46cb3d..5f385fd 100644 --- a/RaspberryDebugger/DebugHelper.cs +++ b/RaspberryDebugger/DebugHelper.cs @@ -283,20 +283,40 @@ private static async Task PublishProjectAsync(DTE2 dte, Solution solution, var errorList = dte?.ToolWindows.ErrorList.ErrorItems; + var warnings = 0; + var messages = 0; if (errorList?.Count > 0) { + var errors = 0; for (var i = 1; i <= errorList.Count; i++) { var error = errorList.Item(i); - Log.Error($"{error.FileName}({error.Line},{error.Column}: {error.Description})"); + switch (error.ErrorLevel) + { + case vsBuildErrorLevel.vsBuildErrorLevelHigh: + Log.Error($"{error.FileName}({error.Line},{error.Column}: {error.Description})"); + errors++; + break; + case vsBuildErrorLevel.vsBuildErrorLevelMedium: + Log.Warning($"{error.FileName}({error.Line},{error.Column}: {error.Description})"); + warnings++; + break; + default: + Log.Info($"{error.FileName}({error.Line},{error.Column}: {error.Description})"); + messages++; + break; + } } - Log.Error($"Build failed: [{errorList.Count}] errors"); - Log.Error("See the Build/Output panel for more information"); - return false; + if (errors > 0) + { + Log.Error($"Build failed: [{errors}] errors"); + Log.Error("See the Build/Output panel for more information"); + return false; + } } - Log.Info("Build succeeded"); + Log.Info($"Build succeeded{(warnings > 0 ? $", with {warnings} warnigns" : null)}{(messages > 0 ? $", with {messages} messages" : null)}"); // Publish the project so all required binaries and assets end up // in the output folder. @@ -312,7 +332,7 @@ private static async Task PublishProjectAsync(DTE2 dte, Solution solution, await Task.Yield(); const string allowedVariableNames = - @" + """ ALLUSERSPROFILE APPDATA architecture @@ -351,7 +371,7 @@ private static async Task PublishProjectAsync(DTE2 dte, Solution solution, USERNAME USERPROFILE windir - "; + """; var allowedVariables = new HashSet(StringComparer.InvariantCultureIgnoreCase); var environmentVariables = new Dictionary(); @@ -570,6 +590,22 @@ public static ConnectionInfo GetDebugConnectionInfo(ProjectProperties projectPro return null; } + // Ensure that linux libraries are installed. + if (!await connection.SetupLinuxDependenciesAsync()) + { + await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); + + MessageBoxEx.Show( + "Cannot install the Linux dependencies on the Raspberry.\r\n\r\nCheck the Debug Output for more details.", + "Linux Dependencies Installation Failed", + MessageBoxButtons.OK, + MessageBoxIcon.Error); + + connection.Dispose(); + + return null; + } + // Upload the program binaries. if (await connection.UploadProgramAsync( projectProperties?.Name, @@ -592,4 +628,3 @@ public static ConnectionInfo GetDebugConnectionInfo(ProjectProperties projectPro } } } - diff --git a/RaspberryDebugger/Properties/AssemblyInfo.cs b/RaspberryDebugger/Properties/AssemblyInfo.cs index 22bdae8..83fc188 100644 --- a/RaspberryDebugger/Properties/AssemblyInfo.cs +++ b/RaspberryDebugger/Properties/AssemblyInfo.cs @@ -45,5 +45,5 @@ // 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")] +[assembly: AssemblyVersion("3.3.0.0")] +[assembly: AssemblyFileVersion("3.3.0.0")] diff --git a/RaspberryDebugger/RaspberryDebugger.csproj b/RaspberryDebugger/RaspberryDebugger.csproj index 7350d32..e037e24 100644 --- a/RaspberryDebugger/RaspberryDebugger.csproj +++ b/RaspberryDebugger/RaspberryDebugger.csproj @@ -153,21 +153,21 @@ 15.0.6142705 - 17.2.32505.113 + 17.5.33428.366 15.8.243 - + compile; build; native; contentfiles; analyzers; buildtransitive - 17.2.32505.113 + 17.7.37355 - 16.3.36 + 16.3.42 - + runtime; build; native; contentfiles; analyzers; buildtransitive all @@ -181,16 +181,16 @@ 2.18.2 - 13.0.1 + 13.0.3 2.9.1 - 7.2.3 + 7.2.4 - 2.11.35 + 2.16.36 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 +