aboutsummaryrefslogtreecommitdiff
path: root/src/install/bin.zig
diff options
context:
space:
mode:
Diffstat (limited to 'src/install/bin.zig')
-rw-r--r--src/install/bin.zig169
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;
+ },
+ }
+ }
+ };
};