Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

V2 #359

Merged
merged 7 commits into from
Sep 28, 2023
Merged

V2 #359

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading