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 a8ec951..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 RemoteDeployAppPath => 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 72888b6..4c973a2 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; @@ -55,12 +53,19 @@ public string Bash(string command) /// Clear entire base deployment folder (TRUE) or just our project. 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) - Bash($"rm -rf {_opts.RemoteDeployBasePath}/*"); + { + Bash($"rm -rf \"{_opts.RemoteDeployBasePath}/{filesAndFolders}\""); + } else - Bash($"rm -rf \"{_launch.RemoteDeployAppPath}/*\""); + { + Bash($"rm -rf {_launch.RemoteDeployProjectFolder}/{filesAndFolders}"); + } } public bool Connect() @@ -76,7 +81,6 @@ public bool Connect() else keyFile = new PrivateKeyFile(_opts.UserPrivateKeyPath, _opts.UserPrivateKeyPassword); } - } catch (Exception ex) { @@ -177,30 +181,28 @@ 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. // 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`. - // TODO: Use base folder path: var pathTarGz = $"{_opts.RemoteDeployBasePath}/{_tarGzFileName}"; - - //// var destTarGz = $"{RemoteDeployPath}/{_tarGzFileName}"; - var destTarGz = LinuxPath.Combine(_launch.RemoteDeployFolder, _tarGzFileName); + var destTarGz = LinuxPath.Combine(_launch.RemoteDeployProjectFolder, _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; } @@ -295,18 +297,12 @@ 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. /// 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 +310,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; - - // TODO: Add new cache file entry - //// UpdateCacheEntry.WriteToStream(newCacheFileWriter, file.Key, file.Value); + fileWriter.Write(localFiles.Count); - updateFileCount++; - updateFileSize += file.Value.Length; + var updateFileCount = 0; + long updateFileSize = 0; + var allFileCount = 0; + long allFileSize = 0; - try - { - tarGzWriter.Write(file.Key, file.Value); - } - catch (IOException ioEx) + foreach (var file in localFiles) { - LogOutput($"Exception: {ioEx.Message}"); + 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}"; + } } - catch (Exception ex) - { - LogOutput($"Exception: {ex.Message}\n{ex.StackTrace}"); - } - } - 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,16 +375,20 @@ 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); - LogOutput($"Uploaded '{_tarGzFileName}' [{tarGzSize,13:n0} bytes]."); + sftp.UploadFile(tarGzStream, pathBuildTarGz); + }); + + 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; } } @@ -394,20 +401,26 @@ 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 += $";cd \"{_launch.RemoteDeployProjectFolder}\""; cmd += $";tar -zxf \"{_tarGzFileName}\""; ////cmd += $";tar -zxf \"{pathBuildTarGz}\""; 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..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; @@ -41,18 +44,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