const libarchive = @import("libarchive"); const curl = @import("curl"); const std = @import("std"); const xdg = @import("xdg"); const zup = @import("zup"); const Allocator = std.mem.Allocator; const Config = zup.Config; const ArchiveRead = libarchive.Read; const Installation = zup.Installation; const Installations = zup.Installations; pub const params = \\-f, --force Install over existing installations \\ ; pub const description = "Installs a Zig version. Run `zup list -i` to see installed s."; pub const min_args = 1; pub const max_args = 1; pub fn main(comptime Result: type, config: Config, res: Result) !void { const allocator = config.allocator; var available = try Installation.getAvailableList(config); defer Installation.deinitMap(allocator, &available); return perform(allocator, res.positionals[0], res.args.force, available); } pub fn perform(allocator: Allocator, name: []const u8, force: bool, available: Installations) !void { if (!force and try Installation.isInstalled(allocator, name)) { std.log.err("{s} already installed, not overwriting without --force!", .{name}); return error.AlreadyInstalled; } const installation = available.get(name) orelse { std.log.err("Installation by name {s} not available!", .{name}); return error.InstallationNotFound; }; if (installation.url == null) { std.log.err("No tarball URL for {s} found!", .{name}); } const installation_dir = blk: { const data_home = try xdg.getDataHome(allocator, "zup"); defer allocator.free(data_home); break :blk try std.fs.path.join(allocator, &.{ data_home, name }); }; defer allocator.free(installation_dir); std.log.info("Installing {s}, version {}", .{ name, installation.version }); const filename = std.fs.path.basename(installation.url.?); // TODO: Platform-agnostic tempfile creation var tmpname = try std.fmt.allocPrintZ(allocator, "/tmp/{s}", .{filename}); defer allocator.free(tmpname); var tmpdir = try std.fs.openDirAbsolute(std.fs.path.dirname(tmpname).?, .{}); defer tmpdir.close(); var tmpfile = try tmpdir.createFile(filename, .{}); defer { tmpfile.close(); std.log.info("Deleting /tmp/{s}...", .{filename}); tmpdir.deleteFile(filename) catch |err| { std.log.warn("Failed to delete /tmp/{s}: {}", .{ filename, err }); }; } std.log.info("Downloading to /tmp/{s}...", .{filename}); try curl.easyDownloadToFile(&tmpfile, installation.url.?); std.log.info("Extracting...", .{}); var archive = try ArchiveRead.init(); defer archive.deinit(); try archive.supportFilter(.all); try archive.supportFormat(.all); try archive.openFilename(tmpname, 10240); while (try archive.nextHeader()) |entry_c| { var entry = entry_c; const source = entry.pathname(); const dest = try preparePathname(allocator, installation_dir, source); defer allocator.free(dest); entry.setPathname(dest); try archive.extract(entry, 0); } std.log.info("Installed to {s}", .{installation_dir}); if (!try Installation.isActive(allocator, name)) { std.log.info("If you want to use this installation, run `zup switch {s}`", .{name}); } } fn preparePathname(allocator: Allocator, installation_dir: []const u8, source: []const u8) ![:0]u8 { const stripped = if (std.mem.indexOfScalar(u8, source, '/')) |idx| source[idx + 1 ..] else ""; return std.fs.path.joinZ(allocator, &.{ installation_dir, stripped }); }