Skip to content

Commit

Permalink
Merge pull request #359 from WildernessLabs/v2
Browse files Browse the repository at this point in the history
V2
  • Loading branch information
ctacke authored Sep 28, 2023
2 parents 4badc53 + 1d10487 commit 50a216b
Show file tree
Hide file tree
Showing 20 changed files with 446 additions and 74 deletions.
4 changes: 2 additions & 2 deletions Meadow.CLI/Properties/launchSettings.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
},
"FileDelete": {
"commandName": "Project",
"commandLineArgs": "file delete -f"
"commandLineArgs": "file delete -f dns.conf"
},
"FileList": {
"commandName": "Project",
Expand Down Expand Up @@ -106,7 +106,7 @@
},
"UsePort": {
"commandName": "Project",
"commandLineArgs": "use port COM10"
"commandLineArgs": "use port COM7"
},
"Version": {
"commandName": "Project",
Expand Down
52 changes: 30 additions & 22 deletions Source/v2/Meadow.Cli/AppManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,30 +27,23 @@ private static bool IsXmlDoc(string file)
}

public static async Task DeployApplication(
IPackageManager packageManager,
IMeadowConnection connection,
string localBinaryDirectory,
bool includePdbs,
bool includeXmlDocs,
ILogger logger,
CancellationToken cancellationToken)
{
// in order to deploy, the runtime must be disabled
var wasRuntimeEnabled = await connection.IsRuntimeEnabled();

if (wasRuntimeEnabled)
{
logger.LogInformation("Disabling runtime...");

await connection.RuntimeDisable(cancellationToken);
}

// TODO: add sub-folder support when HCOM supports it

var localFiles = new Dictionary<string, uint>();

// get a list of files to send
var dependencies = packageManager.GetDependencies(new FileInfo(Path.Combine(localBinaryDirectory, "App.dll")));

logger.LogInformation("Generating the list of files to deploy...");
foreach (var file in Directory.GetFiles(localBinaryDirectory))
foreach (var file in dependencies)
{
// TODO: add any other filtering capability here

Expand All @@ -70,21 +63,36 @@ public static async Task DeployApplication(
}

// get a list of files on-device, with CRCs
var deviceFiles = await connection.GetFileList(true, cancellationToken);
var deviceFiles = await connection.GetFileList(true, cancellationToken) ?? Array.Empty<MeadowFileInfo>();

// get a list of files of the device files that are not in the list we intend to deploy
var removeFiles = deviceFiles
.Select(f => Path.GetFileName(f.Name))
.Except(localFiles.Keys
.Select(f => Path.GetFileName(f)));

// erase all files on device not in list of files to send

// send any file that has a different CRC


if (wasRuntimeEnabled)
// delete those files
foreach (var file in removeFiles)
{
// restore runtime state
logger.LogInformation("Enabling runtime...");

await connection.RuntimeEnable(cancellationToken);
logger.LogInformation($"Deleting file '{file}'...");
await connection.DeleteFile(file, cancellationToken);
}

// now send all files with differing CRCs
foreach (var localFile in localFiles)
{
var existing = deviceFiles.FirstOrDefault(f => Path.GetFileName(f.Name) == Path.GetFileName(localFile.Key));

if (existing != null)
{
if (uint.Parse(existing.Crc.Substring(2), System.Globalization.NumberStyles.HexNumber) == localFile.Value)
{
// exists and has a matching CRC, skip it
continue;
}
}

await connection?.WriteFile(localFile.Key, null, cancellationToken);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ protected override async ValueTask ExecuteCommand(CancellationToken cancellation

if (Configuration == null) Configuration = "Release";

Logger.LogInformation($"Building {Configuration} configuration of of {path}...");
Logger.LogInformation($"Building {Configuration} configuration of {path}...");

// TODO: enable cancellation of this call
var success = _packageManager.BuildApplication(path, Configuration);
Expand Down
68 changes: 55 additions & 13 deletions Source/v2/Meadow.Cli/Commands/Current/App/AppDeployCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,15 @@ namespace Meadow.CLI.Commands.DeviceManagement;
[Command("app deploy", Description = "Deploys a built Meadow application to a target device")]
public class AppDeployCommand : BaseDeviceCommand<AppDeployCommand>
{
private IPackageManager _packageManager;

[CommandParameter(0, Name = "Path to folder containing the built application", IsRequired = false)]
public string? Path { get; set; } = default!;

public AppDeployCommand(MeadowConnectionManager connectionManager, ILoggerFactory loggerFactory)
public AppDeployCommand(IPackageManager packageManager, MeadowConnectionManager connectionManager, ILoggerFactory loggerFactory)
: base(connectionManager, loggerFactory)
{
_packageManager = packageManager;
}

protected override async ValueTask ExecuteCommand(IMeadowConnection connection, Hcom.IMeadowDevice device, CancellationToken cancellationToken)
Expand All @@ -23,6 +26,33 @@ protected override async ValueTask ExecuteCommand(IMeadowConnection connection,
: Path;

// is the path a file?
FileInfo file;

var lastFile = string.Empty;

// in order to deploy, the runtime must be disabled
var wasRuntimeEnabled = await connection.IsRuntimeEnabled();
if (wasRuntimeEnabled)
{
Logger.LogInformation("Disabling runtime...");

await connection.RuntimeDisable(cancellationToken);
}

connection.FileWriteProgress += (s, e) =>
{
var p = (e.completed / (double)e.total) * 100d;
if (e.fileName != lastFile)
{
Console.Write("\n");
lastFile = e.fileName;
}
// Console instead of Logger due to line breaking for progress bar
Console.Write($"Writing {e.fileName}: {p:0}% \r");
};

if (!File.Exists(path))
{
// is it a valid directory?
Expand All @@ -31,27 +61,39 @@ protected override async ValueTask ExecuteCommand(IMeadowConnection connection,
Logger.LogError($"Invalid application path '{path}'");
return;
}

// does the directory have an App.dll in it?
file = new FileInfo(System.IO.Path.Combine(path, "App.dll"));
if (!file.Exists)
{
// it's a directory - we need to determine the latest build (they might have a Debug and a Release config)
var candidates = Cli.PackageManager.GetAvailableBuiltConfigurations(path, "App.dll");

if (candidates.Length == 0)
{
Logger.LogError($"Cannot find a compiled application at '{path}'");
return;
}

file = candidates.OrderByDescending(c => c.LastWriteTime).First();
}
}
else
{
// TODO: only deploy if it's App.dll
file = new FileInfo(path);
}

// do we have the full app path, or just the project root?
var targetDirectory = file.DirectoryName;

// TODO: determine the latest build
await AppManager.DeployApplication(_packageManager, connection, targetDirectory, true, false, Logger, cancellationToken);

await AppManager.DeployApplication(connection, "", true, false, Logger, cancellationToken);

var success = false;

if (!success)
{
Logger.LogError($"Build failed!");
}
else
if (wasRuntimeEnabled)
{
Logger.LogError($"Build success.");
// restore runtime state
Logger.LogInformation("Enabling runtime...");

await connection.RuntimeEnable(cancellationToken);
}
}
}
163 changes: 163 additions & 0 deletions Source/v2/Meadow.Cli/Commands/Current/App/AppRunCommand.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
using CliFx.Attributes;
using Meadow.Cli;
using Meadow.Hcom;
using Microsoft.Extensions.Logging;

namespace Meadow.CLI.Commands.DeviceManagement;

[Command("app run", Description = "Builds, trims and deploys a Meadow application to a target device")]
public class AppRunCommand : BaseDeviceCommand<AppRunCommand>
{
private IPackageManager _packageManager;
private string _lastFile;

[CommandOption("no-prefix", 'n', IsRequired = false, Description = "When set, the message source prefix (e.g. 'stdout>') is suppressed during 'listen'")]
public bool NoPrefix { get; set; }

[CommandOption('c', Description = "The build configuration to compile", IsRequired = false)]
public string? Configuration { get; set; }

[CommandParameter(0, Name = "Path to folder containing the built application", IsRequired = false)]
public string? Path { get; set; } = default!;

public AppRunCommand(IPackageManager packageManager, MeadowConnectionManager connectionManager, ILoggerFactory loggerFactory)
: base(connectionManager, loggerFactory)
{
_packageManager = packageManager;
}

protected override async ValueTask ExecuteCommand(IMeadowConnection connection, Hcom.IMeadowDevice device, CancellationToken cancellationToken)
{
string path = Path == null
? AppDomain.CurrentDomain.BaseDirectory
: Path;

if (!Directory.Exists(path))
{
Logger.LogError($"Target directory '{path}' not found.");
return;
}

var lastFile = string.Empty;

// in order to deploy, the runtime must be disabled
var wasRuntimeEnabled = await connection.IsRuntimeEnabled();
if (wasRuntimeEnabled)
{
Logger.LogInformation("Disabling runtime...");

await connection.RuntimeDisable(cancellationToken);
}

if (!await BuildApplication(path, cancellationToken))
{
return;
}

if (!await TrimApplication(path, cancellationToken))
{
return;
}

// illink returns before all files are actually written. That's not fun, but we must just wait a little while.
await Task.Delay(1000);

if (!await DeployApplication(connection, path, cancellationToken))
{
return;
}

Logger.LogInformation("Enabling the runtime...");
await connection.RuntimeEnable(cancellationToken);

Logger.LogInformation("Listening for messages from Meadow...\n");
connection.DeviceMessageReceived += OnDeviceMessageReceived;

while (!cancellationToken.IsCancellationRequested)
{
await Task.Delay(1000);
}

Logger.LogInformation("Listen cancelled...");
}

private Task<bool> BuildApplication(string path, CancellationToken cancellationToken)
{
if (Configuration == null) Configuration = "Debug";

Logger.LogInformation($"Building {Configuration} configuration of {path}...");

// TODO: enable cancellation of this call
return Task.FromResult(_packageManager.BuildApplication(path, Configuration));
}

private async Task<bool> TrimApplication(string path, CancellationToken cancellationToken)
{
// it's a directory - we need to determine the latest build (they might have a Debug and a Release config)
var candidates = Cli.PackageManager.GetAvailableBuiltConfigurations(path, "App.dll");

if (candidates.Length == 0)
{
Logger.LogError($"Cannot find a compiled application at '{path}'");
return false;
}

var file = candidates.OrderByDescending(c => c.LastWriteTime).First();

// if no configuration was provided, find the most recently built
Logger.LogInformation($"Trimming {file.FullName} (this may take a few seconds)...");

await _packageManager.TrimApplication(file, false, null, cancellationToken);

return true;
}

private async Task<bool> DeployApplication(IMeadowConnection connection, string path, CancellationToken cancellationToken)
{
connection.FileWriteProgress += OnFileWriteProgress;

var candidates = Cli.PackageManager.GetAvailableBuiltConfigurations(path, "App.dll");

if (candidates.Length == 0)
{
Logger.LogError($"Cannot find a compiled application at '{path}'");
return false;
}

var file = candidates.OrderByDescending(c => c.LastWriteTime).First();

Logger.LogInformation($"Deploying app from {file.DirectoryName}...");

await AppManager.DeployApplication(_packageManager, connection, file.DirectoryName, true, false, Logger, cancellationToken);

connection.FileWriteProgress -= OnFileWriteProgress;

return true;
}

private void OnFileWriteProgress(object? sender, (string fileName, long completed, long total) e)
{
var p = (e.completed / (double)e.total) * 100d;

if (e.fileName != _lastFile)
{
Console.Write("\n");
_lastFile = e.fileName;
}

// Console instead of Logger due to line breaking for progress bar
Console.Write($"Writing {e.fileName}: {p:0}% \r");
}

private void OnDeviceMessageReceived(object? sender, (string message, string? source) e)
{
if (NoPrefix)
{
Logger.LogInformation($"{e.message.TrimEnd('\n', '\r')}");
}
else
{
Logger.LogInformation($"{e.source}> {e.message.TrimEnd('\n', '\r')}");
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ protected override async ValueTask ExecuteCommand(CancellationToken cancellation
return;
}

// it's a directory - we need to determine the latest build (they might have a Debug and Release config)
// it's a directory - we need to determine the latest build (they might have a Debug and a Release config)
var candidates = PackageManager.GetAvailableBuiltConfigurations(path, "App.dll");

if (candidates.Length == 0)
Expand Down
Loading

0 comments on commit 50a216b

Please sign in to comment.