diff --git a/README.md b/README.md index 41617b7..fd5945a 100644 --- a/README.md +++ b/README.md @@ -41,7 +41,13 @@ zigup run ... # How the compilers are managed -zigup stores each compiler in a global "install directory" in a versioned subdirectory. On posix systems the "install directory" is `$HOME/zig` and on windows the install directory will be a directory named "zig" in the same directory as the "zigup.exe". +zigup stores each compiler in a global "install directory" in a versioned subdirectory. The "install directory" is a subdirectory called `zigup` of a platform-specific directory. The platform-specific parent directories are: + + - `XDG_DATA_HOME` on Linux—*i.e.* `$XDG_DATA_HOME` with a default of `~/.local/share`, + - `~/Library/Application Support/zigup` on macOS, + - `%APPDATA/zigup` on Windows. + +Using the build option `-Ddefault-dir=[DIR]` changes the install directory so that on posix systems the install directory is `$HOME/[DIR]` and on windows the install directory will be a directory named `[DIR]` in the same directory as the "zigup.exe". zigup makes the zig program available by creating an entry in a directory that occurs in the `PATH` environment variable. On posix systems this entry is a symlink to one of the `zig` executables in the install directory. On windows this is an executable that forwards invocations to one of the `zig` executables in the install directory. diff --git a/build.zig b/build.zig index 90d6f3b..2dfd614 100644 --- a/build.zig +++ b/build.zig @@ -5,8 +5,27 @@ pub fn build(b: *std.Build) !void { const target = b.standardTargetOptions(.{}); const optimize = b.standardOptimizeOption(.{}); + const default_dir = b.option( + []const u8, + "default-dir", + "Use a static default install directory", + ); + + const build_options = b.addOptions(); + build_options.addOption(bool, "use_known_folders", default_dir == null); + if (default_dir) |dir| { + build_options.addOption([]const u8, "default_dir", dir); + } + + const known_folders = if (default_dir != null) + null + else if (b.lazyDependency("known_folders", .{})) |pkg| + pkg.module("known-folders") + else + null; + const zigup_exe_native = blk: { - const exe = addZigupExe(b, target, optimize); + const exe = addZigupExe(b, target, optimize, build_options, known_folders); b.installArtifact(exe); const run_cmd = b.addRunArtifact(exe); run_cmd.step.dependOn(b.getInstallStep()); @@ -74,13 +93,15 @@ pub fn build(b: *std.Build) !void { ci_step.dependOn(test_step); ci_step.dependOn(unzip_step); ci_step.dependOn(zip_step); - try ci(b, ci_step, test_step, host_zip_exe); + try ci(b, ci_step, test_step, host_zip_exe, build_options, known_folders); } fn addZigupExe( b: *std.Build, target: std.Build.ResolvedTarget, optimize: std.builtin.Mode, + build_options: *std.Build.Step.Options, + known_folders: ?*std.Build.Module, ) *std.Build.Step.Compile { const win32exelink_mod: ?*std.Build.Module = blk: { if (target.result.os.tag == .windows) { @@ -104,6 +125,12 @@ fn addZigupExe( .optimize = optimize, }); + exe.root_module.addOptions("build-options", build_options); + + if (known_folders) |mod| { + exe.root_module.addImport("known-folders", mod); + } + if (target.result.os.tag == .windows) { exe.root_module.addImport("win32exelink", win32exelink_mod.?); } @@ -115,6 +142,8 @@ fn ci( ci_step: *std.Build.Step, test_step: *std.Build.Step, host_zip_exe: *std.Build.Step.Compile, + build_options: *std.Build.Step.Options, + known_folders: ?*std.Build.Module, ) !void { const ci_targets = [_][]const u8{ "x86_64-linux", @@ -134,13 +163,13 @@ fn ci( var previous_test_step = test_step; for (ci_targets) |ci_target_str| { - const target = b.resolveTargetQuery(try std.zig.CrossTarget.parse( + const target = b.resolveTargetQuery(try std.Target.Query.parse( .{ .arch_os_abi = ci_target_str }, )); const optimize: std.builtin.OptimizeMode = // Compile in ReleaseSafe on Windows for faster extraction if (target.result.os.tag == .windows) .ReleaseSafe else .Debug; - const zigup_exe = addZigupExe(b, target, optimize); + const zigup_exe = addZigupExe(b, target, optimize, build_options, known_folders); const zigup_exe_install = b.addInstallArtifact(zigup_exe, .{ .dest_dir = .{ .override = .{ .custom = ci_target_str } }, }); diff --git a/build.zig.zon b/build.zig.zon index de6b93f..9b5bde0 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -1,6 +1,13 @@ .{ .name = "zigup", .version = "0.0.1", + .dependencies = .{ + .known_folders = .{ + .url = "https://github.com/ziglibs/known-folders/archive/47076c6b11214a218e9244471d8762310820911a.tar.gz", + .hash = "12209d2738a2e1dbd3781c2e5f01a2ea877dcfeea53efdfa1913247297d328e6b207", + .lazy = true, + }, + }, .paths = .{ "LICENSE", diff --git a/zigup.zig b/zigup.zig index 2919dca..27bf7d6 100644 --- a/zigup.zig +++ b/zigup.zig @@ -2,6 +2,9 @@ const std = @import("std"); const builtin = @import("builtin"); const mem = std.mem; +const build_options = @import("build-options"); +const known_folders = @import("known-folders"); + const ArrayList = std.ArrayList; const Allocator = mem.Allocator; @@ -126,19 +129,28 @@ fn getHomeDir() ![]const u8 { } fn allocInstallDirString(allocator: Allocator) ![]const u8 { + if (build_options.use_known_folders) { + if (try known_folders.getPath(allocator, .data)) |path| { + defer allocator.free(path); + return std.fs.path.join(allocator, &.{ path, "zigup" }); + } else { + std.log.err("Could not find install directory", .{}); + return error.AlreadyReported; + } + } // TODO: maybe support ZIG_INSTALL_DIR environment variable? // TODO: maybe support a file on the filesystem to configure install dir? if (builtin.os.tag == .windows) { const self_exe_dir = try std.fs.selfExeDirPathAlloc(allocator); defer allocator.free(self_exe_dir); - return std.fs.path.join(allocator, &.{ self_exe_dir, "zig" }); + return std.fs.path.join(allocator, &.{ self_exe_dir, build_options.default_dir }); } const home = try getHomeDir(); if (!std.fs.path.isAbsolute(home)) { std.log.err("$HOME environment variable '{s}' is not an absolute path", .{home}); return error.BadHomeEnvironmentVariable; } - return std.fs.path.join(allocator, &[_][]const u8{ home, "zig" }); + return std.fs.path.join(allocator, &[_][]const u8{ home, build_options.default_dir }); } const GetInstallDirOptions = struct { create: bool, @@ -501,7 +513,7 @@ pub fn loggySymlinkAbsolute(target_path: []const u8, sym_link_path: []const u8, /// 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.Dir.SymLinkFlags) !bool { - var current_target_path_buffer: [std.fs.MAX_PATH_BYTES]u8 = undefined; + 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)) { loginfo("symlink '{s}' already points to '{s}'", .{ sym_link_path, target_path }); @@ -631,7 +643,7 @@ fn cleanCompilers(allocator: Allocator, compiler_name_opt: ?[]const u8) !void { } } } -fn readDefaultCompiler(allocator: Allocator, buffer: *[std.fs.MAX_PATH_BYTES + 1]u8) !?[]const u8 { +fn readDefaultCompiler(allocator: Allocator, buffer: *[std.fs.max_path_bytes + 1]u8) !?[]const u8 { const path_link = try makeZigPathLinkString(allocator); defer allocator.free(path_link); @@ -651,7 +663,7 @@ fn readDefaultCompiler(allocator: Allocator, buffer: *[std.fs.MAX_PATH_BYTES + 1 return try allocator.dupe(u8, targetPathToVersion(target_exe)); } - const target_path = std.fs.readLinkAbsolute(path_link, buffer[0..std.fs.MAX_PATH_BYTES]) catch |e| switch (e) { + const target_path = std.fs.readLinkAbsolute(path_link, buffer[0..std.fs.max_path_bytes]) catch |e| switch (e) { error.FileNotFound => return null, else => return e, }; @@ -662,7 +674,7 @@ fn targetPathToVersion(target_path: []const u8) []const u8 { return std.fs.path.basename(std.fs.path.dirname(std.fs.path.dirname(target_path).?).?); } -fn readMasterDir(buffer: *[std.fs.MAX_PATH_BYTES]u8, install_dir: *std.fs.Dir) !?[]const u8 { +fn readMasterDir(buffer: *[std.fs.max_path_bytes]u8, install_dir: *std.fs.Dir) !?[]const u8 { if (builtin.os.tag == .windows) { var file = install_dir.openFile("master", .{}) catch |e| switch (e) { error.FileNotFound => return null, @@ -678,7 +690,7 @@ fn readMasterDir(buffer: *[std.fs.MAX_PATH_BYTES]u8, install_dir: *std.fs.Dir) ! } fn getDefaultCompiler(allocator: Allocator) !?[]const u8 { - var buffer: [std.fs.MAX_PATH_BYTES + 1]u8 = undefined; + var buffer: [std.fs.max_path_bytes + 1]u8 = undefined; const slice_path = (try readDefaultCompiler(allocator, &buffer)) orelse return null; const path_to_return = try allocator.alloc(u8, slice_path.len); @memcpy(path_to_return, slice_path); @@ -686,7 +698,7 @@ fn getDefaultCompiler(allocator: Allocator) !?[]const u8 { } fn getMasterDir(allocator: Allocator, install_dir: *std.fs.Dir) !?[]const u8 { - var buffer: [std.fs.MAX_PATH_BYTES]u8 = undefined; + var buffer: [std.fs.max_path_bytes]u8 = undefined; const slice_path = (try readMasterDir(&buffer, install_dir)) orelse return null; const path_to_return = try allocator.alloc(u8, slice_path.len); @memcpy(path_to_return, slice_path); @@ -773,7 +785,7 @@ fn verifyPathLink(allocator: Allocator, path_link: []const u8) !void { break :blk ""; }; - var path_it = std.mem.tokenize(u8, path_env, ";"); + var path_it = std.mem.tokenizeScalar(u8, path_env, ';'); while (path_it.next()) |path| { switch (try compareDir(path_link_dir_id, path)) { .missing => continue, @@ -789,7 +801,7 @@ fn verifyPathLink(allocator: Allocator, path_link: []const u8) !void { try enforceNoZig(path_link, exe); } - var ext_it = std.mem.tokenize(u8, pathext_env, ";"); + var ext_it = std.mem.tokenizeScalar(u8, pathext_env, ';'); while (ext_it.next()) |ext| { if (ext.len == 0) continue; const basename = try std.mem.concat(allocator, u8, &.{ "zig", ext }); @@ -802,7 +814,7 @@ fn verifyPathLink(allocator: Allocator, path_link: []const u8) !void { } } } else { - var path_it = std.mem.tokenize(u8, std.posix.getenv("PATH") orelse "", ":"); + var path_it = std.mem.tokenizeScalar(u8, std.posix.getenv("PATH") orelse "", ':'); while (path_it.next()) |path| { switch (try compareDir(path_link_dir_id, path)) { .missing => continue, @@ -924,8 +936,8 @@ const win32exelink = struct { }; }; fn createExeLink(link_target: []const u8, path_link: []const u8) !void { - if (path_link.len > std.fs.MAX_PATH_BYTES) { - std.debug.print("Error: path_link (size {}) is too large (max {})\n", .{ path_link.len, std.fs.MAX_PATH_BYTES }); + if (path_link.len > std.fs.max_path_bytes) { + std.debug.print("Error: path_link (size {}) is too large (max {})\n", .{ path_link.len, std.fs.max_path_bytes }); return error.AlreadyReported; } const file = std.fs.cwd().createFile(path_link, .{}) catch |err| switch (err) {