Something went wrong. Try again.
Completely reimplimented in zig (eventually)
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459const std = @import("std");const mem = std.mem;const ascii = std.ascii;const process = std.process;const meta = std.meta;const Init = process.Init;const Io = std.Io;const File = Io.File;const Allocator = std.mem.Allocator;const ArrayList = std.ArrayList;const Environ = process.Environ;
const cli = @import("cli");const engine = @import("engine.zig");const style = @import("style.zig");
/// Print a styled diagnostic on stderr and exit(1).fn fail(init: Init, comptime fmt: []const u8, args: anytype) noreturn { var buffer: [256]u8 = undefined; var w = File.stderr().writer(init.io, &buffer); style.errPrefix(init.io, &w.interface) catch {}; w.interface.print(fmt, args) catch {}; w.interface.flush() catch {}; process.exit(1);}
const Writers = struct { stdout: File = .stdout(), stderr: File = .stderr(),
const E = enum { stdout, stderr };
pub fn write(self: *Writers, io: Io, writer: E, bytes: []const u8) !void { const file = switch (writer) { .stdout => self.stdout, .stderr => self.stderr, }; var buffer: [4096]u8 = undefined; var w = file.writer(io, &buffer); try w.interface.writeAll(bytes); try w.interface.flush(); }
pub fn print(self: *Writers, io: Io, writer: E, comptime fmt: []const u8, args: anytype) !void { const file = switch (writer) { .stdout => self.stdout, .stderr => self.stderr, }; var buffer: [256]u8 = undefined; var w = file.writer(io, &buffer); try w.interface.print(fmt, args); try w.interface.flush(); }};
const MPLABX_VERSION = "6.35";
const Filter = enum { installed, all, updated };
const Restrict = enum { tools, devices };
fn resolveUserRepo(allocator: Allocator, env: *Environ.Map) []const u8 { if (env.get("packslib.userpacksfolder")) |p| return p; if (env.get("MPLABX_PACKS_DIR")) |p| return p; const home = env.get("HOME") orelse return ""; return std.fmt.allocPrint(allocator, "{s}/.mchp_packs", .{home}) catch "";}
fn resolveSystemRepo(env: *Environ.Map) []const u8 { return env.get("packslib.packsfolder") orelse "";}
fn isPath(name: []const u8) bool { return mem.find(u8, name, "/") != null or mem.endsWith(u8, name, ".atpack");}
pub fn list( init: Init, filter: cli.Param( "filter", Filter, .optional(.installed), "Filter by status: installed, all, updates", ), restrict_val: cli.Param( "restrict", ?Restrict, .optional(null), "Filter by type: tools, devices", ), device_name: cli.Param( "device-name", ?[]const u8, .optional(null), "Filter by device name", ),) !void { const allocator = init.arena.allocator(); const user_repo = resolveUserRepo(allocator, init.environ_map); const system_repo = resolveSystemRepo(init.environ_map);
var out: ArrayList(u8) = .empty;
const lopts: engine.ListOptions = .{ .all = filter.value == .all, .updates = filter.value == .updated, .dfps = restrict_val.value == .devices and device_name.value == null, .tps = restrict_val.value == .tools and device_name.value == null, .device_name = device_name.value, };
var writers: Writers = .{};
engine.listPacks(init.io, allocator, user_repo, system_repo, lopts, &out) catch |err| { fail(init, "listing packs: {}\n", .{err}); };
try writers.write(init.io, .stdout, out.items);}
pub fn install( init: Init, name: cli.Param( "name", []const u8, .required, "Pack name, device name, or path to .atpack file", ), version: cli.Param( "version", []const u8, .optional("latest"), "Package version (remote only)", ), vendor: cli.Param( "vendor", []const u8, .optional("Microchip"), "Package vendor (remote only)", ), location: cli.Param( "location", []const u8, .optional(""), "Install location for disk installs (defaults to user repo)", ),) !void { const allocator = init.arena.allocator(); const user_repo = resolveUserRepo(allocator, init.environ_map); const system_repo = resolveSystemRepo(init.environ_map);
var writers: Writers = .{};
// 1. Path detection if (isPath(name.value)) { if (!engine.fileExists(init.io, name.value)) { fail(init, "file not found: {s}\n", .{name.value}); } const install_location = if (location.value.len > 0) location.value else user_repo; engine.installFromDisk(init.io, allocator, name.value, install_location) catch |err| { fail(init, "failed to install pack from disk: {s} ({})\n", .{ name.value, err }); }; return; }
// Update remote index (unless installing from disk, which we're not here) if (user_repo.len > 0) { _ = engine.updateIndex(init.io, allocator, user_repo); }
// 2. Pack name lookup const entries = engine.loadIndexEntries(init.io, allocator, user_repo, system_repo) catch |err| { fail(init, "reading pack index: {}\n", .{err}); };
if (engine.indexLatestVersion(entries.items, name.value)) |_| { // Pack found in index — resolve version const resolve_version = if (mem.eql(u8, version.value, "latest")) engine.indexLatestVersion(entries.items, name.value).? else version.value;
const packs = engine.scanInstalledPacks( init.io, allocator, user_repo, system_repo, ) catch |err| { fail(init, "reading installed packs: {}\n", .{err}); }; if (engine.isInstalledVersion(packs.items, name.value, vendor.value, resolve_version)) { fail(init, "pack {s} version {s} is already installed\n", .{ name.value, resolve_version }); }
engine.installPackFromRemote( init.io, allocator, name.value, vendor.value, resolve_version, user_repo, ) catch |err| { fail(init, "unable to install pack {s} {s}: {}\n", .{ name.value, resolve_version, err }); }; const out = std.fmt.allocPrint( allocator, "Installed {s} {s}\n", .{ name.value, resolve_version }, ) catch "Installed\n"; try writers.write(init.io, .stdout, out); return; }
// 3. Device name lookup const device_matches = engine.findPackForDevice( allocator, entries.items, name.value, ) catch |err| { fail(init, "searching for device: {}\n", .{err}); };
if (device_matches.len == 1) { const upd = device_matches[0]; const packs = engine.scanInstalledPacks(init.io, allocator, user_repo, system_repo) catch |err| { fail(init, "reading installed packs: {}\n", .{err}); }; if (engine.isInstalledVersion(packs.items, upd.name, upd.vendor, upd.version)) { fail(init, "pack {s} version {s} is already installed\n", .{ upd.name, upd.version }); } engine.installPackFromRemote( init.io, allocator, upd.name, upd.vendor, upd.version, user_repo, ) catch |err| { fail(init, "unable to install pack {s} {s}: {}\n", .{ upd.name, upd.version, err }); }; const out = std.fmt.allocPrint( allocator, "Installed {s} {s}\n", .{ upd.name, upd.version }, ) catch "Installed\n"; try writers.write(init.io, .stdout, out); return; }
if (device_matches.len > 1) { var ebuf: [256]u8 = undefined; var ew = File.stderr().writer(init.io, &ebuf); try style.errPrefix(init.io, &ew.interface); try ew.interface.print("multiple packs found for device '{s}':\n", .{name.value}); for (device_matches) |upd| { try ew.interface.print(" {s} {s} ({s})\n", .{ upd.name, upd.version, upd.vendor }); } try ew.interface.writeAll("specify the pack name directly\n"); try ew.interface.flush(); process.exit(1); }
fail(init, "no pack found for device: {s}\n", .{name.value});}
pub fn uninstall( init: Init, name: cli.Param( "name", []const u8, .required, "Package name", ), version: cli.Param( "version", []const u8, .required, "Package version", ), vendor: cli.Param( "vendor", []const u8, .optional("Microchip"), "Package vendor", ),) !void { const allocator = init.arena.allocator(); const user_repo = resolveUserRepo(allocator, init.environ_map); const system_repo = resolveSystemRepo(init.environ_map);
var writers: Writers = .{};
const packs = engine.scanInstalledPacks( init.io, allocator, user_repo, system_repo, ) catch |err| { fail(init, "reading installed packs: {}\n", .{err}); };
if (!engine.isInstalledVersion(packs.items, name.value, vendor.value, version.value)) { fail(init, "selected pack version is not installed: {s} version {s}\n", .{ name.value, version.value }); }
try engine.deletePackDir( init.io, allocator, user_repo, vendor.value, name.value, version.value, ); const out = std.fmt.allocPrint(allocator, "Uninstalled {s} {s}\n", .{ name.value, version.value }) catch "Uninstalled\n"; try writers.write(init.io, .stdout, out);}
pub fn details( init: Init, name: cli.Param( "name", []const u8, .required, "Pack or device name", ),) !void { const allocator = init.arena.allocator(); const user_repo = resolveUserRepo(allocator, init.environ_map); const system_repo = resolveSystemRepo(init.environ_map);
const packs_list = engine.scanInstalledPacks(init.io, allocator, user_repo, system_repo) catch |err| { fail(init, "reading installed packs: {}\n", .{err}); }; const entries = engine.loadIndexEntries(init.io, allocator, user_repo, system_repo) catch |err| { fail(init, "reading pack index: {}\n", .{err}); };
var found: ?*engine.Pack = null; resolve: { for (packs_list.items) |*p| { if (mem.eql(u8, p.name, name.value)) { found = p; break :resolve; } } // Fall back to matching a supported device name. for (packs_list.items) |*p| { for (p.devices.items) |d| { if (ascii.eqlIgnoreCase(d, name.value)) { found = p; break :resolve; } } } } const p = found orelse fail(init, "unknown pack or unsupported device: {s}\n", .{name.value});
const ui = style.Ui.detect(init.io, init.environ_map); var out_buf: [8192]u8 = undefined; var w = File.stdout().writer(init.io, &out_buf); const o = &w.interface;
const ptype = engine.latestReleaseType(allocator, p) catch .unknown; const is_dfp = (ptype == .dfp or ptype == .unknown); const installed = engine.maxInstalledVersion(allocator, p) catch null orelse "None"; const latest = engine.latestReleaseVersion(allocator, p) catch "None";
// Header block. try ui.strong(o, p.name); try o.writeAll("\n"); try printField(o, ui, "Vendor", p.vendor, .plain); try printField(o, ui, "Type", engine.typeString(ptype), .plain); try printField(o, ui, "Installed", installed, if (mem.eql(u8, installed, latest)) .good else .warn); try printField(o, ui, "Latest", latest, .plain);
// Device list, flowed into columns. if (is_dfp and p.devices.items.len > 0) { try o.writeAll("\n"); try ui.header(o, "Devices"); try o.print(" ({d})\n", .{p.devices.items.len});
const rows = try style.flowColumns(allocator, p.devices.items, ui.width -| 3); defer style.freeRows(allocator, rows); for (rows) |row| { try o.writeAll(" "); try o.writeAll(row); try o.writeAll("\n"); } }
// Tools that support this pack's devices. if (is_dfp) { const tools = engine.dependentTools(allocator, MPLABX_VERSION, p, entries.items); if (tools.items.len > 0) { try o.writeAll("\n"); try ui.header(o, "Dependent tools"); try o.print(" ({d})\n", .{tools.items.len});
const rows = try style.flowColumns(allocator, tools.items, ui.width -| 3); defer style.freeRows(allocator, rows); for (rows) |row| { try o.writeAll(" "); try o.writeAll(row); try o.writeAll("\n"); } } }
try w.interface.flush();}
const FieldTone = enum { plain, good, warn };
fn printField(o: *std.Io.Writer, ui: style.Ui, comptime label: []const u8, value: []const u8, tone: FieldTone) !void { try ui.dim(o, " "); try o.print(label ++ ":", .{}); // Align values in a fixed column regardless of label width. var pad: usize = 10 -| label.len; while (pad > 0) : (pad -= 1) try o.writeAll(" "); switch (tone) { .plain => try o.writeAll(value), .good => try ui.good(o, value), .warn => try ui.warn(o, value), } try o.writeAll("\n");}
pub fn update(init: Init) !void { const allocator = init.arena.allocator(); const user_repo = resolveUserRepo(allocator, init.environ_map); const system_repo = resolveSystemRepo(init.environ_map);
var writers: Writers = .{};
const index_result = engine.updateIndex(init.io, allocator, user_repo); switch (index_result) { .up_to_date => try writers.write(init.io, .stdout, "No changes.\n"), .updated => try writers.write(init.io, .stdout, "Index updated.\n"), .error_no_etag => fail(init, "could not update index\n", .{}), }
const updates = engine.packUpdates(init.io, allocator, user_repo, system_repo) catch |err| { fail(init, "checking for updates: {}\n", .{err}); };
for (updates.items) |u| { engine.installPackFromRemote(init.io, allocator, u.name, u.vendor, u.version, user_repo) catch |err| { fail(init, "unable to install pack {s} {s}: {}\n", .{ u.name, u.version, err }); }; const out = std.fmt.allocPrint(allocator, "Installed {s} {s}\n", .{ u.name, u.version }) catch "Installed\n"; try writers.write(init.io, .stdout, out); }
if (updates.items.len == 0) { try writers.write(init.io, .stdout, "All packs are up to date.\n"); }}