diff options
Diffstat (limited to 'src/install/bin.zig')
| -rw-r--r-- | src/install/bin.zig | 169 |
1 files changed, 168 insertions, 1 deletions
diff --git a/src/install/bin.zig b/src/install/bin.zig index db94a6432..fb343a16d 100644 --- a/src/install/bin.zig +++ b/src/install/bin.zig @@ -3,7 +3,10 @@ const Semver = @import("./semver.zig"); const ExternalString = Semver.ExternalString; const String = Semver.String; const std = @import("std"); - +const strings = @import("strings"); +const Environment = @import("../env.zig"); +const Path = @import("../resolver/resolve_path.zig"); +const C = @import("../c.zig"); /// Normalized `bin` field in [package.json](https://docs.npmjs.com/cli/v8/configuring-npm/package-json#bin) /// Can be a: /// - file path (relative to the package root) @@ -117,4 +120,168 @@ pub const Bin = extern struct { ///``` map = 4, }; + + pub const Linker = struct { + bin: Bin, + + package_installed_node_modules: std.os.fd_t = std.math.maxInt(std.os.fd_t), + root_node_modules_folder: std.os.fd_t = std.math.maxInt(std.os.fd_t), + + /// Used for generating relative paths + package_name: strings.StringOrTinyString, + + string_buf: []const u8, + + err: ?anyerror = null, + + pub var umask: std.os.mode_t = 0; + + pub const Error = error{ + NotImplementedYet, + } || std.os.SymLinkError || std.os.OpenError || std.os.RealPathError; + + // It is important that we use symlinkat(2) with relative paths instead of symlink() + // That way, if you move your node_modules folder around, the symlinks in .bin still work + // If we used absolute paths for the symlinks, you'd end up with broken symlinks + pub fn link(this: *Linker) void { + var from_buf: [std.fs.MAX_PATH_BYTES]u8 = undefined; + var path_buf: [std.fs.MAX_PATH_BYTES]u8 = undefined; + + from_buf[0..".bin/".len].* = ".bin/".*; + var from_remain: []u8 = from_buf[".bin/".len..]; + path_buf[0.."../".len].* = "../".*; + + var remain: []u8 = path_buf["../".len..]; + const name = this.package_name.slice(); + std.mem.copy(u8, remain, name); + remain = remain[name.len..]; + remain[0] = std.fs.path.sep; + remain = remain[1..]; + const base_len = @ptrToInt(remain.ptr) - @ptrToInt(&path_buf); + + if (comptime Environment.isWindows) { + @compileError("Bin.Linker.link() needs to be updated to generate .cmd files on Windows"); + } + + switch (this.bin.tag) { + .none => { + if (comptime Environment.isDebug) { + unreachable; + } + }, + .file => { + var target = this.bin.value.file.slice(this.string_buf); + if (strings.hasPrefix(target, "./")) { + target = target[2..]; + } + std.mem.copy(u8, remain, target); + remain = remain[target.len..]; + remain[0] = 0; + const target_len = @ptrToInt(remain.ptr) - @ptrToInt(&path_buf); + remain = remain[1..]; + + var target_path: [:0]u8 = path_buf[0..target_len :0]; + std.mem.copy(u8, from_remain, name); + from_remain = from_remain[name.len..]; + from_remain[0] = 0; + var dest_path: [:0]u8 = from_buf[0 .. @ptrToInt(from_remain.ptr) - @ptrToInt(&from_buf) :0]; + + _ = C.chmod(target_path, umask & 0o777); + std.os.symlinkatZ(target_path, this.root_node_modules_folder, dest_path) catch |err| { + // Silently ignore PathAlreadyExists + // Most likely, the symlink was already created by another package + if (err == error.PathAlreadyExists) return; + + this.err = err; + }; + }, + .named_file => { + var target = this.bin.value.named_file[1].slice(this.string_buf); + if (strings.hasPrefix(target, "./")) { + target = target[2..]; + } + std.mem.copy(u8, remain, target); + remain = remain[target.len..]; + remain[0] = 0; + const target_len = @ptrToInt(remain.ptr) - @ptrToInt(&path_buf); + remain = remain[1..]; + + var target_path: [:0]u8 = path_buf[0..target_len :0]; + var name_to_use = this.bin.value.named_file[0].slice(this.string_buf); + std.mem.copy(u8, from_remain, name_to_use); + from_remain = from_remain[name_to_use.len..]; + from_remain[0] = 0; + var dest_path: [:0]u8 = from_buf[0 .. @ptrToInt(from_remain.ptr) - @ptrToInt(&from_buf) :0]; + + _ = C.chmod(target_path, umask & 0o777); + std.os.symlinkatZ(target_path, this.root_node_modules_folder, dest_path) catch |err| { + // Silently ignore PathAlreadyExists + // Most likely, the symlink was already created by another package + if (err == error.PathAlreadyExists) return; + + this.err = err; + }; + }, + .dir => { + var target = this.bin.value.dir.slice(this.string_buf); + var parts = [_][]const u8{ name, target }; + if (strings.hasPrefix(target, "./")) { + target = target[2..]; + } + std.mem.copy(u8, remain, target); + remain = remain[target.len..]; + remain[0] = 0; + var dir = std.fs.Dir{ .fd = this.package_installed_node_modules }; + + var joined = Path.joinStringBuf(&from_buf, &parts, .auto); + from_buf[joined.len] = 0; + var joined_: [:0]u8 = from_buf[0..joined.len :0]; + var child_dir = dir.openDirZ(joined_, .{ .iterate = true }) catch |err| { + this.err = err; + return; + }; + defer child_dir.close(); + + var iter = child_dir.iterate(); + + var basedir_path = std.os.getFdPath(child_dir.fd, &from_buf) catch |err| { + this.err = err; + return; + }; + from_buf[basedir_path.len] = std.fs.path.sep; + var from_buf_remain = from_buf[basedir_path.len + 1 ..]; + + while (iter.next() catch null) |entry_| { + const entry: std.fs.Dir.Entry = entry_; + switch (entry.kind) { + std.fs.Dir.Entry.Kind.SymLink, std.fs.Dir.Entry.Kind.File => { + std.mem.copy(u8, from_buf_remain, entry.name); + from_buf_remain = from_buf_remain[entry.name.len..]; + from_buf_remain[0] = 0; + var from_path: [:0]u8 = from_buf[0 .. @ptrToInt(from_buf_remain.ptr) - @ptrToInt(&from_buf) :0]; + var to_path = std.fmt.bufPrintZ(&path_buf, ".bin/{s}", .{entry.name}) catch unreachable; + _ = C.chmod(from_path, umask & 0o777); + std.os.symlinkatZ( + from_path, + this.root_node_modules_folder, + to_path, + ) catch |err| { + // Silently ignore PathAlreadyExists + // Most likely, the symlink was already created by another package + if (err == error.PathAlreadyExists) return; + + this.err = err; + return; + }; + }, + else => {}, + } + } + }, + .map => { + this.err = error.NotImplementedYet; + }, + } + } + }; }; |
