From 134d9e672e87bbbd67196feac6fd8bb9bc952e3c Mon Sep 17 00:00:00 2001 From: Damian Suess Date: Mon, 2 May 2022 18:10:28 -0400 Subject: [PATCH 1/2] Upload files async #2. Enhanced Output #7 and removal of `launch.json` if it previously existed #14 --- src/VsLinuxDebugger/Core/LaunchBuilder.cs | 2 +- src/VsLinuxDebugger/Core/SshTool.cs | 133 ++++++++++-------- src/VsLinuxDebugger/Logger.cs | 7 +- .../Properties/AssemblyInfo.cs | 4 +- .../source.extension.vsixmanifest | 2 +- 5 files changed, 86 insertions(+), 62 deletions(-) diff --git a/src/VsLinuxDebugger/Core/LaunchBuilder.cs b/src/VsLinuxDebugger/Core/LaunchBuilder.cs index a8ec951..fed3503 100644 --- a/src/VsLinuxDebugger/Core/LaunchBuilder.cs +++ b/src/VsLinuxDebugger/Core/LaunchBuilder.cs @@ -55,7 +55,7 @@ public LaunchBuilder(DTE2 dte, Project dteProject, UserOptions o) public string ProjectName { get; set; } /// Full path to the remote assembly. (i.e. `/home/USER/VLSDbg/Proj/ConsoleApp1.dll`) - public string RemoteDeployAppPath => LinuxPath.Combine(RemoteDeployFolder, $"{AssemblyName}.dll"); + public string RemoteDeployAssemblyFilePath => LinuxPath.Combine(RemoteDeployFolder, $"{AssemblyName}.dll"); /// Folder of our remote assembly. (i.e. `/home/USER/VLSDbg/Proj`) public string RemoteDeployFolder => diff --git a/src/VsLinuxDebugger/Core/SshTool.cs b/src/VsLinuxDebugger/Core/SshTool.cs index 72888b6..ffdcdd0 100644 --- a/src/VsLinuxDebugger/Core/SshTool.cs +++ b/src/VsLinuxDebugger/Core/SshTool.cs @@ -3,9 +3,7 @@ using System.Collections.Generic; using System.IO; using System.Threading.Tasks; -using Microsoft.VisualStudio; using Microsoft.VisualStudio.Shell; -using Microsoft.VisualStudio.Shell.Interop; using Renci.SshNet; using SharpCompress.Common; using SharpCompress.Writers; @@ -53,14 +51,23 @@ public string Bash(string command) /// Cleans the contents of the deployment path. /// Clear entire base deployment folder (TRUE) or just our project. - public void CleanDeploymentFolder(bool fullScrub = false) + public void CleanDeploymentFolder(bool fullScrub = true) { //// Bash($"sudo rm -rf {_opts.RemoteDeployBasePath}/*"); - + if (fullScrub) - Bash($"rm -rf {_opts.RemoteDeployBasePath}/*"); + { + // Whole deployment folder and hidden files + // rm -rf xxx/* == Contents of the folder but not the folder itself + // rm -rf xxx/{*,.*} == All hidden files and folders + var filesAndFolders = "{*,.*}"; + Bash($"rm -rf \"{_opts.RemoteDeployBasePath}/{filesAndFolders}\""); + } else - Bash($"rm -rf \"{_launch.RemoteDeployAppPath}/*\""); + { + // Full path to the file we'll execute (i.e. "/home/USER/VsLinuxDbg/PROJECT/AppName.dll"). + Bash($"rm -rf \"{_launch.RemoteDeployAssemblyFilePath}\""); + } } public bool Connect() @@ -76,7 +83,6 @@ public bool Connect() else keyFile = new PrivateKeyFile(_opts.UserPrivateKeyPath, _opts.UserPrivateKeyPassword); } - } catch (Exception ex) { @@ -177,7 +183,8 @@ public async Task UploadFilesAsync() { try { - Bash($@"mkdir -p {_launch.RemoteDeployFolder}"); + // Clean output folder just incase + //// Bash($@"rm -rf {_launch.RemoteDeployFolder}"); // TODO: Rev1 - Iterate through each file and upload it via SCP client or SFTP. // TODO: Rev2 - Compress _localHost.OutputDirFullName, upload ZIP, and unzip it. @@ -191,16 +198,13 @@ public async Task UploadFilesAsync() throw new DirectoryNotFoundException($"Directory '{_launch.OutputDirFullPath}' not found!"); // Compress files to upload as single `tar.gz`. - // TODO: Use base folder path: var pathTarGz = $"{_opts.RemoteDeployBasePath}/{_tarGzFileName}"; - - //// var destTarGz = $"{RemoteDeployPath}/{_tarGzFileName}"; var destTarGz = LinuxPath.Combine(_launch.RemoteDeployFolder, _tarGzFileName); Logger.Output($"Destination Tar.GZ: '{destTarGz}'"); - var success = PayloadCompressAndUpload(_sftp, srcDirInfo, destTarGz); + var success = await PayloadCompressAndUploadAsync(_sftp, srcDirInfo, destTarGz); // Decompress file - PayloadDecompress(destTarGz, false); + await PayloadDecompressAsync(destTarGz, false); return string.Empty; } @@ -306,7 +310,7 @@ private void LogOutput(string message) /// Build (source) contents directory info. /// Upload path and filename of build's tar.gz file. /// - private bool PayloadCompressAndUpload(SftpClient sftp, DirectoryInfo srcDirInfo, string pathBuildTarGz) + private async Task PayloadCompressAndUploadAsync(SftpClient sftp, DirectoryInfo srcDirInfo, string pathBuildTarGz) { var success = false; var localFiles = GetLocalFiles(srcDirInfo); @@ -314,57 +318,64 @@ private bool PayloadCompressAndUpload(SftpClient sftp, DirectoryInfo srcDirInfo, // TODO: Delta remote files against local files for changes. using (Stream tarGzStream = new MemoryStream()) { - try + var outputMsg = string.Empty; + + await Task.Run(() => { - using (var tarGzWriter = WriterFactory.Open(tarGzStream, ArchiveType.Tar, CompressionType.GZip)) + try { - using (MemoryStream fileStream = new MemoryStream()) + using (var tarGzWriter = WriterFactory.Open(tarGzStream, ArchiveType.Tar, CompressionType.GZip)) { - using (BinaryWriter fileWriter = new BinaryWriter(fileStream)) + using (MemoryStream fileStream = new MemoryStream()) { - fileWriter.Write(localFiles.Count); - - var updateFileCount = 0; - long updateFileSize = 0; - var allFileCount = 0; - long allFileSize = 0; - - foreach (var file in localFiles) + using (BinaryWriter fileWriter = new BinaryWriter(fileStream)) { - allFileCount++; - allFileSize += file.Value.Length; + fileWriter.Write(localFiles.Count); - // TODO: Add new cache file entry - //// UpdateCacheEntry.WriteToStream(newCacheFileWriter, file.Key, file.Value); + var updateFileCount = 0; + long updateFileSize = 0; + var allFileCount = 0; + long allFileSize = 0; - updateFileCount++; - updateFileSize += file.Value.Length; - - try - { - tarGzWriter.Write(file.Key, file.Value); - } - catch (IOException ioEx) - { - LogOutput($"Exception: {ioEx.Message}"); - } - catch (Exception ex) + foreach (var file in localFiles) { - LogOutput($"Exception: {ex.Message}\n{ex.StackTrace}"); + allFileCount++; + allFileSize += file.Value.Length; + + // TODO: Add new cache file entry + //// UpdateCacheEntry.WriteToStream(newCacheFileWriter, file.Key, file.Value); + + updateFileCount++; + updateFileSize += file.Value.Length; + + try + { + tarGzWriter.Write(file.Key, file.Value); + } + catch (IOException ioEx) + { + outputMsg += $"Exception: {ioEx.Message}{Environment.NewLine}"; + } + catch (Exception ex) + { + outputMsg += $"Exception: {ex.Message}{Environment.NewLine}{ex.StackTrace}{Environment.NewLine}"; + } } - } - LogOutput($"{updateFileCount,7:n0} [{updateFileSize,13:n0} bytes] of {allFileCount,7:n0} [{allFileSize,13:n0} bytes] files need to be updated"); + outputMsg += $"Update file count: {updateFileCount}; File Size: [{updateFileSize} bytes] of Total Files: {allFileCount} [{allFileSize} bytes] need to be updated"; + } } } + + success = true; } + catch (Exception ex) + { + outputMsg += $"Error while compressing file contents. {ex.Message}\n{ex.StackTrace}"; + } + }); - success = true; - } - catch (Exception ex) - { - LogOutput($"Error while compressing file contents. {ex.Message}\n{ex.StackTrace}"); - } + Logger.Output(outputMsg); // Upload the file if (success) @@ -372,9 +383,13 @@ private bool PayloadCompressAndUpload(SftpClient sftp, DirectoryInfo srcDirInfo, try { var tarGzSize = tarGzStream.Length; - tarGzStream.Seek(0, SeekOrigin.Begin); - sftp.UploadFile(tarGzStream, pathBuildTarGz); + await Task.Run(() => + { + tarGzStream.Seek(0, SeekOrigin.Begin); + + sftp.UploadFile(tarGzStream, pathBuildTarGz); + }); LogOutput($"Uploaded '{_tarGzFileName}' [{tarGzSize,13:n0} bytes]."); success = true; @@ -394,10 +409,12 @@ private bool PayloadCompressAndUpload(SftpClient sftp, DirectoryInfo srcDirInfo, /// Path to upload to. /// Remove our build's tar.gz file. Set to FALSE for debugging. (default=true) /// Returns true on success. - private bool PayloadDecompress(string pathBuildTarGz, bool removeTarGz = true) + private async Task PayloadDecompressAsync(string pathBuildTarGz, bool removeTarGz = true) { try { + var decompressOutput = string.Empty; + var cmd = "set -e"; cmd += $";cd \"{_launch.RemoteDeployFolder}\""; cmd += $";tar -zxf \"{_tarGzFileName}\""; @@ -406,8 +423,12 @@ private bool PayloadDecompress(string pathBuildTarGz, bool removeTarGz = true) if (removeTarGz) cmd += $";rm \"{pathBuildTarGz}\""; - var output = Bash(cmd); - LogOutput(output); + await Task.Run(() => + { + decompressOutput = Bash(cmd); + }); + + Logger.Output($"Payload Decompress results: '{decompressOutput}' (blank=OK)"); return true; } diff --git a/src/VsLinuxDebugger/Logger.cs b/src/VsLinuxDebugger/Logger.cs index cae8eb1..3174eec 100644 --- a/src/VsLinuxDebugger/Logger.cs +++ b/src/VsLinuxDebugger/Logger.cs @@ -41,18 +41,21 @@ public static void Init(IServiceProvider provider, OutputWindowType outputType = public static void Output(string message) { + var msg = $"{FormattedTime}: {message}{Environment.NewLine}"; + try { ThreadHelper.ThrowIfNotOnUIThread(); if (HasOutputWindow()) { - _outputPane.OutputStringThreadSafe($"{FormattedTime}: {message}{Environment.NewLine}"); - _outputPane.Activate(); // Brings this pane into view + _outputPane.OutputStringThreadSafe(msg); + _outputPane.Activate(); // Brings pane into view } } catch (Exception) { + Console.Write($"Failed to Output: '{msg}'"); } } diff --git a/src/VsLinuxDebugger/Properties/AssemblyInfo.cs b/src/VsLinuxDebugger/Properties/AssemblyInfo.cs index ad3ef13..c608071 100644 --- a/src/VsLinuxDebugger/Properties/AssemblyInfo.cs +++ b/src/VsLinuxDebugger/Properties/AssemblyInfo.cs @@ -29,5 +29,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("1.8.0.0")] -[assembly: AssemblyFileVersion("1.8.0.0")] +[assembly: AssemblyVersion("1.8.1.0")] +[assembly: AssemblyFileVersion("1.8.1.0")] diff --git a/src/VsLinuxDebugger/source.extension.vsixmanifest b/src/VsLinuxDebugger/source.extension.vsixmanifest index 729c1b5..4420e48 100644 --- a/src/VsLinuxDebugger/source.extension.vsixmanifest +++ b/src/VsLinuxDebugger/source.extension.vsixmanifest @@ -1,7 +1,7 @@ - + VS Linux Debugger Remotely deploy and debug your .NET apps visa SSH on your Linux device using Visual Studio 2022. Works with popular Linux distrobutions such as Ubuntu, Raspberry Pi, and more! https://github.com/SuessLabs/VsLinuxDebug From 88f2abcdab72171cad7914dd9e4fcebdd4f0de12 Mon Sep 17 00:00:00 2001 From: Damian Suess Date: Mon, 2 May 2022 18:28:32 -0400 Subject: [PATCH 2/2] Fixed folder cleanup. Added more logging. Updated Output Window drop-down title to "Linux Debugger" from "Remote Debugger" --- src/VsLinuxDebugger/Commands.Impl.cs | 4 +-- src/VsLinuxDebugger/Core/LaunchBuilder.cs | 11 ++++---- src/VsLinuxDebugger/Core/RemoteDebugger.cs | 2 +- src/VsLinuxDebugger/Core/SshTool.cs | 32 ++++++++-------------- src/VsLinuxDebugger/Logger.cs | 5 +++- 5 files changed, 24 insertions(+), 30 deletions(-) diff --git a/src/VsLinuxDebugger/Commands.Impl.cs b/src/VsLinuxDebugger/Commands.Impl.cs index 4017a71..2902b12 100644 --- a/src/VsLinuxDebugger/Commands.Impl.cs +++ b/src/VsLinuxDebugger/Commands.Impl.cs @@ -36,13 +36,13 @@ private async Task ExecuteBuildAsync(BuildOptions buildOptions) if (!dbg.IsProjectValid()) { - Console.WriteLine("No C# startup project/solution loaded."); + Logger.Output("No C# startup project/solution loaded."); return false; } if (!await dbg.BeginAsync(buildOptions)) { - Console.WriteLine("Failed to perform actions."); + Logger.Output("Failed to perform actions."); return false; } diff --git a/src/VsLinuxDebugger/Core/LaunchBuilder.cs b/src/VsLinuxDebugger/Core/LaunchBuilder.cs index fed3503..be9e248 100644 --- a/src/VsLinuxDebugger/Core/LaunchBuilder.cs +++ b/src/VsLinuxDebugger/Core/LaunchBuilder.cs @@ -55,11 +55,10 @@ public LaunchBuilder(DTE2 dte, Project dteProject, UserOptions o) public string ProjectName { get; set; } /// Full path to the remote assembly. (i.e. `/home/USER/VLSDbg/Proj/ConsoleApp1.dll`) - public string RemoteDeployAssemblyFilePath => LinuxPath.Combine(RemoteDeployFolder, $"{AssemblyName}.dll"); + public string RemoteDeployAssemblyFilePath => LinuxPath.Combine(RemoteDeployProjectFolder, $"{AssemblyName}.dll"); /// Folder of our remote assembly. (i.e. `/home/USER/VLSDbg/Proj`) - public string RemoteDeployFolder => - LinuxPath.Combine(_opts.RemoteDeployBasePath, ProjectName); + public string RemoteDeployProjectFolder => LinuxPath.Combine(_opts.RemoteDeployBasePath, ProjectName); public string RemoteDotNetPath => _opts.RemoteDotNetPath; @@ -90,7 +89,7 @@ public string GenerateLaunchJson(bool vsdbgLogging = false) var vsdbgLogPath = ""; if (vsdbgLogging) - vsdbgLogPath = $" --engineLogging={LinuxPath.Combine(RemoteDeployFolder, "_vsdbg.log")}"; + vsdbgLogPath = $" --engineLogging={LinuxPath.Combine(RemoteDeployProjectFolder, "_vsdbg.log")}"; if (!_opts.LocalPlinkEnabled) { @@ -119,7 +118,7 @@ public string GenerateLaunchJson(bool vsdbgLogging = false) var obj = new Launch( RemoteDotNetPath, $"{AssemblyName}.dll", /// RemoteDeployAppPath, - RemoteDeployFolder, + RemoteDeployProjectFolder, default, false) { @@ -142,7 +141,7 @@ public string GenerateLaunchJson(bool vsdbgLogging = false) } catch (Exception ex) { - Console.WriteLine($"Error writing 'launch.json' to path, '{outputPath}'!\n{ex.Message}"); + Logger.Output($"Error writing 'launch.json' to path, '{outputPath}'!\n{ex.Message}"); outputPath = string.Empty; } diff --git a/src/VsLinuxDebugger/Core/RemoteDebugger.cs b/src/VsLinuxDebugger/Core/RemoteDebugger.cs index f701eb0..ac2ac00 100644 --- a/src/VsLinuxDebugger/Core/RemoteDebugger.cs +++ b/src/VsLinuxDebugger/Core/RemoteDebugger.cs @@ -62,7 +62,7 @@ public async Task BeginAsync(BuildOptions buildOptions) { if (!ssh.Connect()) { - Console.WriteLine("Could not connect to remote device."); + Logger.Output("Could not connect to remote device."); return false; } diff --git a/src/VsLinuxDebugger/Core/SshTool.cs b/src/VsLinuxDebugger/Core/SshTool.cs index ffdcdd0..4c973a2 100644 --- a/src/VsLinuxDebugger/Core/SshTool.cs +++ b/src/VsLinuxDebugger/Core/SshTool.cs @@ -51,22 +51,20 @@ public string Bash(string command) /// Cleans the contents of the deployment path. /// Clear entire base deployment folder (TRUE) or just our project. - public void CleanDeploymentFolder(bool fullScrub = true) + public void CleanDeploymentFolder(bool fullScrub = false) { - //// Bash($"sudo rm -rf {_opts.RemoteDeployBasePath}/*"); + // Whole deployment folder and hidden files + // rm -rf xxx/* == Contents of the folder but not the folder itself + // rm -rf xxx/{*,.*} == All hidden files and folders + var filesAndFolders = "{*,.*}"; if (fullScrub) { - // Whole deployment folder and hidden files - // rm -rf xxx/* == Contents of the folder but not the folder itself - // rm -rf xxx/{*,.*} == All hidden files and folders - var filesAndFolders = "{*,.*}"; Bash($"rm -rf \"{_opts.RemoteDeployBasePath}/{filesAndFolders}\""); } else { - // Full path to the file we'll execute (i.e. "/home/USER/VsLinuxDbg/PROJECT/AppName.dll"). - Bash($"rm -rf \"{_launch.RemoteDeployAssemblyFilePath}\""); + Bash($"rm -rf {_launch.RemoteDeployProjectFolder}/{filesAndFolders}"); } } @@ -189,16 +187,16 @@ public async Task UploadFilesAsync() // TODO: Rev1 - Iterate through each file and upload it via SCP client or SFTP. // TODO: Rev2 - Compress _localHost.OutputDirFullName, upload ZIP, and unzip it. // TODO: Rev3 - Allow for both SFTP and SCP as a backup. This separating connection to a new disposable class. - //// LogOutput($"Connected to {_connectionInfo.Username}@{_connectionInfo.Host}:{_connectionInfo.Port} via SSH and {(_sftpClient != null ? "SFTP" : "SCP")}"); + //// Logger.Output($"Connected to {_connectionInfo.Username}@{_connectionInfo.Host}:{_connectionInfo.Port} via SSH and {(_sftpClient != null ? "SFTP" : "SCP")}"); - Bash($@"mkdir -p {_launch.RemoteDeployFolder}"); + Bash($@"mkdir -p {_launch.RemoteDeployProjectFolder}"); var srcDirInfo = new DirectoryInfo(_launch.OutputDirFullPath); if (!srcDirInfo.Exists) throw new DirectoryNotFoundException($"Directory '{_launch.OutputDirFullPath}' not found!"); // Compress files to upload as single `tar.gz`. - var destTarGz = LinuxPath.Combine(_launch.RemoteDeployFolder, _tarGzFileName); + var destTarGz = LinuxPath.Combine(_launch.RemoteDeployProjectFolder, _tarGzFileName); Logger.Output($"Destination Tar.GZ: '{destTarGz}'"); var success = await PayloadCompressAndUploadAsync(_sftp, srcDirInfo, destTarGz); @@ -299,12 +297,6 @@ private ConcurrentDictionary GetLocalFiles(DirectoryInfo srcDi return localFileCache; } - private void LogOutput(string message) - { - Console.WriteLine($">> {message}"); - Logger.Output(message); - } - /// Compress build contents and upload to remote host. /// SFTP connection. /// Build (source) contents directory info. @@ -391,12 +383,12 @@ await Task.Run(() => sftp.UploadFile(tarGzStream, pathBuildTarGz); }); - LogOutput($"Uploaded '{_tarGzFileName}' [{tarGzSize,13:n0} bytes]."); + Logger.Output($"Uploaded '{_tarGzFileName}' [{tarGzSize,13:n0} bytes]."); success = true; } catch (Exception ex) { - LogOutput($"Error while uploading file. {ex.Message}\n{ex.StackTrace}"); + Logger.Output($"Error while uploading file. {ex.Message}\n{ex.StackTrace}"); success = false; } } @@ -416,7 +408,7 @@ private async Task PayloadDecompressAsync(string pathBuildTarGz, bool remo var decompressOutput = string.Empty; var cmd = "set -e"; - cmd += $";cd \"{_launch.RemoteDeployFolder}\""; + cmd += $";cd \"{_launch.RemoteDeployProjectFolder}\""; cmd += $";tar -zxf \"{_tarGzFileName}\""; ////cmd += $";tar -zxf \"{pathBuildTarGz}\""; diff --git a/src/VsLinuxDebugger/Logger.cs b/src/VsLinuxDebugger/Logger.cs index 3174eec..9562d3b 100644 --- a/src/VsLinuxDebugger/Logger.cs +++ b/src/VsLinuxDebugger/Logger.cs @@ -32,7 +32,10 @@ private static string FormattedTime } } - public static void Init(IServiceProvider provider, OutputWindowType outputType = OutputWindowType.Debug, string name = "Remote Debugger") + public static void Init( + IServiceProvider provider, + OutputWindowType outputType = OutputWindowType.Debug, + string name = "Linux Debugger") { _provider = provider; _outputType = outputType;