123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541 |
- const std = @import("std");
- const builtin = std.builtin;
- const mem = std.mem;
- const ArrayList = std.ArrayList;
- const Allocator = mem.Allocator;
- const ziget = @import("ziget");
- var globalOptionalInstallDir : ?[]const u8 = null;
- var globalOptionalPathLink : ?[]const u8 = null;
- fn find_zigs(allocator: *Allocator) !?[][]u8 {
- const ziglist = std.ArrayList([]u8).init(allocator);
- // don't worry about free for now, this is a short lived program
- if (builtin.os.tag == .windows) {
- @panic("windows not implemented");
- //const result = try runGetOutput(allocator, .{"where", "-a", "zig"});
- } else {
- const whichResult = try cmdlinetool.runGetOutput(allocator, .{"which", "zig"});
- if (runutil.runFailed(&whichResult)) {
- return null;
- }
- if (whichResult.stderr.len > 0) {
- std.debug.warn("which command failed with:\n{}\n", .{whichResult.stderr});
- std.os.exit(1);
- }
- std.debug.warn("which output:\n{}\n", .{whichResult.stdout});
- {var i = std.mem.split(whichResult.stdout, "\n"); while (i.next()) |dir| {
- std.debug.warn("path '{}'\n", .{dir});
- }}
- }
- @panic("not impl");
- }
- fn download(allocator: *Allocator, url: []const u8, writer: anytype) !void {
- var downloadOptions = ziget.request.DownloadOptions {
- .flags = 0,
- .allocator = allocator,
- .maxRedirects = 10,
- .forwardBufferSize = 4096,
- .maxHttpResponseHeaders = 8192,
- .onHttpRequest = ignoreHttpCallback,
- .onHttpResponse = ignoreHttpCallback,
- };
- var downloadState = ziget.request.DownloadState.init();
- try ziget.request.download(
- ziget.url.parseUrl(url) catch unreachable,
- writer,
- downloadOptions,
- &downloadState,
- );
- }
- fn downloadToFileAbsolute(allocator: *Allocator, url: []const u8, fileAbsolute: []const u8) !void {
- const file = try std.fs.createFileAbsolute(fileAbsolute, .{});
- defer file.close();
- try download(allocator, url, file.outStream());
- }
- fn downloadToString(allocator: *Allocator, url: []const u8) ![]u8 {
- var responseArrayList = try ArrayList(u8).initCapacity(allocator, 20 * 1024); // 20 KB (modify if response is expected to be bigger)
- errdefer responseArrayList.deinit();
- try download(allocator, url, responseArrayList.outStream());
- return responseArrayList.toOwnedSlice();
- }
- fn ignoreHttpCallback(request: []const u8) void { }
- fn makeInstallDirString(allocator: *Allocator) ![]const u8 {
- // TODO: maybe support ZIG_INSTALL_DIR environment variable?
- // TODO: maybe support a file on the filesystem to configure install dir?
- const home = std.os.getenv("HOME") orelse {
- std.debug.warn("Error: cannot find install directory, $HOME environment variable is not set\n", .{});
- return error.MissingHomeEnvironmentVariable;
- };
- if (!std.fs.path.isAbsolute(home)) {
- std.debug.warn("Error: $HOME environment variable '{}' is not an absolute path\n", .{home});
- return error.BadHomeEnvironmentVariable;
- }
- return std.fs.path.join(allocator, &[_][]const u8 {home, "zig"});
- }
- fn getAndCreateInstallDir(allocator: *Allocator) ![]const u8 {
- var optionalDirToFreeOnError : ?[]const u8 = null;
- errdefer if (optionalDirToFreeOnError) |dir| allocator.free(dir);
- const installDir = init: {
- if (globalOptionalInstallDir) |dir| break :init dir;
- optionalDirToFreeOnError = try makeInstallDirString(allocator);
- break :init optionalDirToFreeOnError.?;
- };
- std.debug.assert(std.fs.path.isAbsolute(installDir));
- std.debug.warn("install directory '{}'\n", .{installDir});
- loggyMakeDirAbsolute(installDir) catch |e| switch (e) {
- error.PathAlreadyExists => {},
- else => return e,
- };
- return installDir;
- }
- fn makeZigPathLinkString(allocator: *Allocator) ![]const u8 {
- if (globalOptionalPathLink) |path| return path;
- // for now we're just going to hardcode the path to $HOME/bin/zig
- const home = std.os.getenv("HOME") orelse {
- std.debug.warn("Error: cannot find install directory, $HOME environment variable is not set\n", .{});
- return error.MissingHomeEnvironmentVariable;
- };
- return try std.fs.path.join(allocator, &[_][]const u8 {home, "bin", "zig"});
- }
- // TODO: this should be in standard lib
- fn toAbsolute(allocator: *Allocator, path: []const u8) ![] u8 {
- std.debug.assert(!std.fs.path.isAbsolute(path));
- const cwd = try std.process.getCwdAlloc(allocator);
- defer allocator.free(cwd);
- return std.fs.path.join(allocator, &[_][]const u8 {cwd, path});
- }
- fn help() void {
- std.io.getStdErr().writeAll(
- \\Download and manage zig compilers.
- \\
- \\Common Usage:
- \\
- \\ zigup VERSION download and set VERSION compiler as default
- \\ zigup fetch VERSION download VERSION compiler
- \\ zigup default [VERSION] get or set the default compiler
- \\
- \\Uncommon Usage:
- \\
- \\ zigup fetch-index download and print the download index json
- \\
- \\Common Options:
- \\ --install-dir DIR override the default install location
- \\ --path-link PATH path to the `zig` symlink that points to the default compiler
- \\ this will typically be a file path within a PATH directory so
- \\ that the user can just run `zig`
- ) catch unreachable;
- }
- fn getCmdOpt(args: [][]const u8, i: *usize) ![]const u8 {
- i.* += 1;
- if (i.* == args.len) {
- std.debug.warn("Error: option '{}' requires an argument\n", .{args[i.* - 1]});
- return error.AlreadyReported;
- }
- return args[i.*];
- }
- pub fn main() !u8 {
- return main2() catch |e| switch (e) {
- error.AlreadyReported => return 1,
- else => return e,
- };
- }
- pub fn main2() !u8 {
- var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
- const allocator = &arena.allocator;
- const argsArray = try std.process.argsAlloc(allocator);
- // no need to free, os will do it
- //defer std.process.argsFree(allocator, argsArray);
- var args = if (argsArray.len == 0) argsArray else argsArray[1..];
- //
- // parse common options
- //
- {
- var i : usize = 0;
- var newlen : usize = 0;
- while (i < args.len) : (i += 1) {
- const arg = args[i];
- if (std.mem.eql(u8, "--install-dir", arg)) {
- globalOptionalInstallDir = try getCmdOpt(args, &i);
- if (!std.fs.path.isAbsolute(globalOptionalInstallDir.?)) {
- globalOptionalInstallDir = try toAbsolute(allocator, globalOptionalInstallDir.?);
- }
- } else if (std.mem.eql(u8, "--path-link", arg)) {
- globalOptionalPathLink = try getCmdOpt(args, &i);
- if (!std.fs.path.isAbsolute(globalOptionalPathLink.?)) {
- globalOptionalPathLink = try toAbsolute(allocator, globalOptionalPathLink.?);
- }
- } else {
- args[newlen] = args[i];
- newlen += 1;
- }
- }
- args = args[0..newlen];
- }
- if (args.len == 0) {
- help();
- return 1;
- }
- if (std.mem.eql(u8, "fetch-index", args[0])) {
- if (args.len != 1) {
- std.debug.warn("Error: 'index' command requires 0 arguments but got {}\n", .{args.len - 1});
- return 1;
- }
- var downloadIndex = try fetchDownloadIndex(allocator);
- defer downloadIndex.deinit(allocator);
- try std.io.getStdOut().writeAll(downloadIndex.text);
- return 0;
- }
- if (std.mem.eql(u8, "fetch", args[0])) {
- if (args.len != 2) {
- std.debug.warn("Error: 'fetch' command requires 1 argument but got {}\n", .{args.len - 1});
- return 1;
- }
- try fetchCompiler(allocator, args[1], .leaveDefault);
- return 0;
- }
- if (std.mem.eql(u8, "list", args[0])) {
- if (args.len != 1) {
- std.debug.warn("Error: 'list' command requires 0 arguments but got {}\n", .{args.len - 1});
- return 1;
- }
- try listCompilers(allocator);
- return 0;
- }
- if (std.mem.eql(u8, "default", args[0])) {
- if (args.len == 1) {
- try printDefaultCompiler(allocator);
- return 0;
- }
- if (args.len == 2) {
- const versionString = args[1];
- const installDir = try getAndCreateInstallDir(allocator);
- defer allocator.free(installDir);
- const compilerDir = try std.fs.path.join(allocator, &[_][]const u8 {installDir, versionString});
- defer allocator.free(compilerDir);
- if (std.mem.eql(u8, versionString, "latest")) {
- @panic("set default to latest not implemented");
- } else {
- try setDefaultCompiler(allocator, compilerDir);
- }
- return 0;
- }
- std.debug.warn("Error: 'default' command requires 1 or 2 arguments but got {}\n", .{args.len - 1});
- return 1;
- }
- if (args.len == 1) {
- try fetchCompiler(allocator, args[0], .setDefault);
- return 0;
- }
- const command = args[0];
- args = args[1..];
- std.debug.warn("command not impl '{}'\n", .{command});
- return 1;
- //const optionalInstallPath = try find_zigs(allocator);
- }
- const SetDefault = enum { setDefault, leaveDefault };
- fn fetchCompiler(allocator: *Allocator, versionArg: []const u8, setDefault: SetDefault) !void {
- const installDir = try getAndCreateInstallDir(allocator);
- defer allocator.free(installDir);
- var optionalDownloadIndex : ?DownloadIndex = null;
- // This is causing an LLVM error
- //defer if (optionalDownloadIndex) |_| optionalDownloadIndex.?.deinit(allocator);
- // Also I would rather do this, but it doesn't work because of const issues
- //defer if (optionalDownloadIndex) |downloadIndex| downloadIndex.deinit(allocator);
- const VersionUrl = struct { version: []const u8, url: []const u8 };
- const latest = std.mem.eql(u8, versionArg, "latest");
- const versionUrl = blk: {
- if (!latest)
- break :blk VersionUrl { .version = versionArg, .url = try getDefaultUrl(allocator, versionArg) };
- optionalDownloadIndex = try fetchDownloadIndex(allocator);
- const master = optionalDownloadIndex.?.json.root.Object.get("master").?;
- const compilerVersion = master.Object.get("version").?.String;
- const masterLinux = master.Object.get("x86_64-linux").?;
- const masterLinuxTarball = masterLinux.Object.get("tarball").?.String;
- break :blk VersionUrl { .version = compilerVersion, .url = masterLinuxTarball };
- };
- const compilerDir = try std.fs.path.join(allocator, &[_][]const u8 {installDir, versionUrl.version});
- defer allocator.free(compilerDir);
- try installCompiler(allocator, compilerDir, versionUrl.url);
- if (latest) {
- const latestSymlink = try std.fs.path.join(allocator, &[_][]const u8 {installDir, "latest"});
- defer allocator.free(latestSymlink);
- _ = try loggyUpdateSymlink(versionUrl.version, latestSymlink, .{.is_directory=true});
- }
- if (setDefault == .setDefault) {
- try setDefaultCompiler(allocator, compilerDir);
- }
- }
- const downloadIndexUrl = "https://ziglang.org/download/index.json";
- const DownloadIndex = struct {
- text: []u8,
- json: std.json.ValueTree,
- pub fn deinit(self: *DownloadIndex, allocator: *Allocator) void {
- self.json.deinit();
- allocator.free(self.text);
- }
- };
- fn fetchDownloadIndex(allocator: *Allocator) !DownloadIndex {
- const text = downloadToString(allocator, downloadIndexUrl) catch |e| switch (e) {
- else => {
- std.debug.warn("failed to download '{}': {}\n", .{downloadIndexUrl, e});
- return e;
- },
- };
- errdefer allocator.free(text);
- var json = init: {
- var parser = std.json.Parser.init(allocator, false);
- defer parser.deinit();
- break :init try parser.parse(text);
- };
- errdefer json.deinit();
- return DownloadIndex { .text = text, .json = json };
- }
- fn loggyMakeDirAbsolute(dirAbsolute: []const u8) !void {
- if (builtin.os.tag == .windows) {
- std.debug.warn("mkdir \"{}\"\n", .{dirAbsolute});
- } else {
- std.debug.warn("mkdir '{}'\n", .{dirAbsolute});
- }
- try std.fs.makeDirAbsolute(dirAbsolute);
- }
- fn loggyDeleteTreeAbsolute(dirAbsolute: []const u8) !void {
- if (builtin.os.tag == .windows) {
- std.debug.warn("rd /s /q \"{}\"\n", .{dirAbsolute});
- } else {
- std.debug.warn("rm -rf '{}'\n", .{dirAbsolute});
- }
- try std.fs.deleteTreeAbsolute(dirAbsolute);
- }
- pub fn loggyRenameAbsolute(old_path: []const u8, new_path: []const u8) !void {
- std.debug.warn("mv '{}' '{}'\n", .{old_path, new_path});
- try std.fs.renameAbsolute(old_path, new_path);
- }
- pub fn loggySymlinkAbsolute(target_path: []const u8, sym_link_path: []const u8, flags: std.fs.SymLinkFlags) !void {
- std.debug.warn("ln -s '{}' '{}'\n", .{target_path, sym_link_path});
- // NOTE: can't use symLinkAbsolute because it requires target_path to be absolute but we don't want that
- // not sure if it is a bug in the standard lib or not
- //try std.fs.symLinkAbsolute(target_path, sym_link_path, flags);
- try std.os.symlink(target_path, sym_link_path);
- }
- /// returns: true if the symlink was updated, false if it was already set to the given `target_path`
- pub fn loggyUpdateSymlink(target_path: []const u8, sym_link_path: []const u8, flags: std.fs.SymLinkFlags) !bool {
- var current_target_path_buffer: [std.fs.MAX_PATH_BYTES]u8 = undefined;
- if (std.fs.readLinkAbsolute(sym_link_path, ¤t_target_path_buffer)) |current_target_path| {
- if (std.mem.eql(u8, target_path, current_target_path)) {
- std.debug.warn("symlink '{}' already points to '{}'\n", .{sym_link_path, target_path});
- return false; // already up-to-date
- }
- try std.os.unlink(sym_link_path);
- } else |e| switch (e) {
- error.FileNotFound => {},
- else => return e,
- }
- try loggySymlinkAbsolute(target_path, sym_link_path, flags);
- return true; // updated
- }
- // TODO: this should be in std lib somewhere
- fn existsAbsolute(absolutePath: []const u8) !bool {
- std.fs.cwd().access(absolutePath, .{}) catch |e| switch (e) {
- error.FileNotFound => return false,
- error.PermissionDenied => return e,
- error.InputOutput => return e,
- error.SystemResources => return e,
- error.SymLinkLoop => return e,
- error.FileBusy => return e,
- error.Unexpected => unreachable,
- error.InvalidUtf8 => unreachable,
- error.ReadOnlyFileSystem => unreachable,
- error.NameTooLong => unreachable,
- error.BadPathName => unreachable,
- };
- return true;
- }
- fn listCompilers(allocator: *Allocator) !void {
- const installDirString = try makeInstallDirString(allocator);
- defer allocator.free(installDirString);
- var installDir = std.fs.cwd().openDir(installDirString, .{.iterate=true}) catch |e| switch (e) {
- error.FileNotFound => return,
- else => return e,
- };
- defer installDir.close();
- const stdout = std.io.getStdOut().writer();
- {var it = installDir.iterate(); while (try it.next()) |entry| {
- if (entry.kind != .Directory)
- continue;
- if (std.mem.endsWith(u8, entry.name, ".installing"))
- continue;
- try stdout.print("{}\n", .{entry.name});
- }}
- }
- fn printDefaultCompiler(allocator: *Allocator) !void {
- const pathLink = try makeZigPathLinkString(allocator);
- defer allocator.free(pathLink);
- var targetPathBuffer: [std.fs.MAX_PATH_BYTES]u8 = undefined;
- if (std.fs.readLinkAbsolute(pathLink, &targetPathBuffer)) |targetPath| {
- std.debug.warn("{}", .{
- std.fs.path.basename(std.fs.path.dirname(std.fs.path.dirname(targetPath).?).?)});
- } else |e| switch (e) {
- error.FileNotFound => {
- std.debug.warn("<no-default>", .{});
- },
- else => return e,
- }
- }
- fn setDefaultCompiler(allocator: *Allocator, compilerDir: []const u8) !void {
- const pathLink = try makeZigPathLinkString(allocator);
- defer allocator.free(pathLink);
- const linkTarget = try std.fs.path.join(allocator, &[_][]const u8 {compilerDir, "files", "zig"});
- defer allocator.free(linkTarget);
- if (builtin.os.tag == .windows) {
- // TODO: create zig.bat file
- @panic("setDefaultCompiler not implemented in Windows");
- } else {
- _ = try loggyUpdateSymlink(linkTarget, pathLink, .{});
- }
- }
- fn getDefaultUrl(allocator: *Allocator, compilerVersion: []const u8) ![]const u8 {
- return try std.fmt.allocPrint(allocator, "https://ziglang.org/download/{}/zig-linux-x86_64-{}.tar.xz",
- .{compilerVersion, compilerVersion});
- }
- fn installCompiler(allocator: *Allocator, compilerDir: []const u8, url: []const u8) !void {
- if (try existsAbsolute(compilerDir)) {
- std.debug.warn("compiler '{}' already installed\n", .{compilerDir});
- return;
- }
- const installingDir = try std.mem.concat(allocator, u8, &[_][]const u8 {compilerDir, ".installing"});
- defer allocator.free(installingDir);
- try loggyDeleteTreeAbsolute(installingDir);
- try loggyMakeDirAbsolute(installingDir);
- const archiveBasename = std.fs.path.basename(url);
- var archiveRootDir : []const u8 = undefined;
- // download and extract archive
- {
- const archiveAbsolute = try std.fs.path.join(allocator, &[_][]const u8 {installingDir, archiveBasename});
- defer allocator.free(archiveAbsolute);
- std.debug.warn("downloading '{}' to '{}'\n", .{url, archiveAbsolute});
- downloadToFileAbsolute(allocator, url, archiveAbsolute) catch |e| switch (e) {
- error.HttpNon200StatusCode => {
- // TODO: more information would be good
- std.debug.warn("HTTP request failed (TODO: improve ziget library to get better error)\n", .{});
- return error.AlreadyReported;
- },
- else => return e,
- };
- if (std.mem.endsWith(u8, archiveBasename, ".tar.xz")) {
- archiveRootDir = archiveBasename[0.. archiveBasename.len - ".tar.xz".len];
- _ = try run(allocator, &[_][]const u8 {"tar", "xf", archiveAbsolute, "-C", installingDir});
- } else {
- std.debug.warn("Error: unknown archive extension '{}'\n", .{archiveBasename});
- return error.UnknownArchiveExtension;
- }
- try loggyDeleteTreeAbsolute(archiveAbsolute);
- }
- {
- const extractedDir = try std.fs.path.join(allocator, &[_][]const u8 {installingDir, archiveRootDir});
- defer allocator.free(extractedDir);
- const normalizedDir = try std.fs.path.join(allocator, &[_][]const u8 {installingDir, "files"});
- defer allocator.free(normalizedDir);
- try loggyRenameAbsolute(extractedDir, normalizedDir);
- }
- // TODO: write date information (so users can sort compilers by date)
- // finish installation by renaming the install dir
- try loggyRenameAbsolute(installingDir, compilerDir);
- }
- pub fn run(allocator: *std.mem.Allocator, argv: []const []const u8) !std.ChildProcess.Term {
- try logRun(allocator, argv);
- var proc = try std.ChildProcess.init(argv, allocator);
- defer proc.deinit();
- return proc.spawnAndWait();
- }
- fn logRun(allocator: *std.mem.Allocator, argv: []const []const u8) !void {
- var buffer = try allocator.alloc(u8, getCommandStringLength(argv));
- defer allocator.free(buffer);
- var prefix = false;
- var offset : usize = 0;
- for (argv) |arg| {
- if (prefix) {
- buffer[offset] = ' ';
- offset += 1;
- } else {
- prefix = true;
- }
- std.mem.copy(u8, buffer[offset..offset + arg.len], arg);
- offset += arg.len;
- }
- std.debug.assert(offset == buffer.len);
- std.debug.warn("[RUN] {}\n", .{buffer});
- }
- pub fn getCommandStringLength(argv: []const []const u8) usize {
- var len : usize = 0;
- var prefixLength : u8 = 0;
- for (argv) |arg| {
- len += prefixLength + arg.len;
- prefixLength = 1;
- }
- return len;
- }
- pub fn appendCommandString(appender: *appendlib.Appender(u8), argv: []const []const u8) void {
- var prefix : []const u8 = "";
- for (argv) |arg| {
- appender.appendSlice(prefix);
- appender.appendSlice(arg);
- prefix = " ";
- }
- }
|