mirror of
https://github.com/oven-sh/bun
synced 2026-02-09 18:38:55 +00:00
Fixes ENG-21287
Build times, from `bun run build && echo '//' >> src/main.zig && time
bun run build`
|Platform|0.14.1|0.15.2|Speedup|
|-|-|-|-|
|macos debug asan|126.90s|106.27s|1.19x|
|macos debug noasan|60.62s|50.85s|1.19x|
|linux debug asan|292.77s|241.45s|1.21x|
|linux debug noasan|146.58s|130.94s|1.12x|
|linux debug use_llvm=false|n/a|78.27s|1.87x|
|windows debug asan|177.13s|142.55s|1.24x|
Runtime performance:
- next build memory usage may have gone up by 5%. Otherwise seems the
same. Some code with writers may have gotten slower, especially one
instance of a counting writer and a few instances of unbuffered writers
that now have vtable overhead.
- File size reduced by 800kb (from 100.2mb to 99.4mb)
Improvements:
- `@export` hack is no longer needed for watch
- native x86_64 backend for linux builds faster. to use it, set use_llvm
false and no_link_obj false. also set `ASAN_OPTIONS=detect_leaks=0`
otherwise it will spam the output with tens of thousands of lines of
debug info errors. may need to use the zig lldb fork for debugging.
- zig test-obj, which we will be able to use for zig unit tests
Still an issue:
- false 'dependency loop' errors remain in watch mode
- watch mode crashes observed
Follow-up:
- [ ] search `comptime Writer: type` and `comptime W: type` and remove
- [ ] remove format_mode in our zig fork
- [ ] remove deprecated.zig autoFormatLabelFallback
- [ ] remove deprecated.zig autoFormatLabel
- [ ] remove deprecated.BufferedWriter and BufferedReader
- [ ] remove override_no_export_cpp_apis as it is no longer needed
- [ ] css Parser(W) -> Parser, and remove all the comptime writer: type
params
- [ ] remove deprecated writer fully
Files that add lines:
```
649 src/deprecated.zig
167 scripts/pack-codegen-for-zig-team.ts
54 scripts/cleartrace-impl.js
46 scripts/cleartrace.ts
43 src/windows.zig
18 src/fs.zig
17 src/bun.js/ConsoleObject.zig
16 src/output.zig
12 src/bun.js/test/debug.zig
12 src/bun.js/node/node_fs.zig
8 src/env_loader.zig
7 src/css/printer.zig
7 src/cli/init_command.zig
7 src/bun.js/node.zig
6 src/string/escapeRegExp.zig
6 src/install/PnpmMatcher.zig
5 src/bun.js/webcore/Blob.zig
4 src/crash_handler.zig
4 src/bun.zig
3 src/install/lockfile/bun.lock.zig
3 src/cli/update_interactive_command.zig
3 src/cli/pack_command.zig
3 build.zig
2 src/Progress.zig
2 src/install/lockfile/lockfile_json_stringify_for_debugging.zig
2 src/css/small_list.zig
2 src/bun.js/webcore/prompt.zig
1 test/internal/ban-words.test.ts
1 test/internal/ban-limits.json
1 src/watcher/WatcherTrace.zig
1 src/transpiler.zig
1 src/shell/builtin/cp.zig
1 src/js_printer.zig
1 src/io/PipeReader.zig
1 src/install/bin.zig
1 src/css/selectors/selector.zig
1 src/cli/run_command.zig
1 src/bun.js/RuntimeTranspilerStore.zig
1 src/bun.js/bindings/JSRef.zig
1 src/bake/DevServer.zig
```
Files that remove lines:
```
-1 src/test/recover.zig
-1 src/sql/postgres/SocketMonitor.zig
-1 src/sql/mysql/MySQLRequestQueue.zig
-1 src/sourcemap/CodeCoverage.zig
-1 src/css/values/color_js.zig
-1 src/compile_target.zig
-1 src/bundler/linker_context/convertStmtsForChunk.zig
-1 src/bundler/bundle_v2.zig
-1 src/bun.js/webcore/blob/read_file.zig
-1 src/ast/base.zig
-2 src/sql/postgres/protocol/ArrayList.zig
-2 src/shell/builtin/mkdir.zig
-2 src/install/PackageManager/patchPackage.zig
-2 src/install/PackageManager/PackageManagerDirectories.zig
-2 src/fmt.zig
-2 src/css/declaration.zig
-2 src/css/css_parser.zig
-2 src/collections/baby_list.zig
-2 src/bun.js/bindings/ZigStackFrame.zig
-2 src/ast/E.zig
-3 src/StandaloneModuleGraph.zig
-3 src/deps/picohttp.zig
-3 src/deps/libuv.zig
-3 src/btjs.zig
-4 src/threading/Futex.zig
-4 src/shell/builtin/touch.zig
-4 src/meta.zig
-4 src/install/lockfile.zig
-4 src/css/selectors/parser.zig
-5 src/shell/interpreter.zig
-5 src/css/error.zig
-5 src/bun.js/web_worker.zig
-5 src/bun.js.zig
-6 src/cli/test_command.zig
-6 src/bun.js/VirtualMachine.zig
-6 src/bun.js/uuid.zig
-6 src/bun.js/bindings/JSValue.zig
-9 src/bun.js/test/pretty_format.zig
-9 src/bun.js/api/BunObject.zig
-14 src/install/install_binding.zig
-14 src/fd.zig
-14 src/bun.js/node/path.zig
-14 scripts/pack-codegen-for-zig-team.sh
-17 src/bun.js/test/diff_format.zig
```
`git diff --numstat origin/main...HEAD | awk '{ print ($1-$2)"\t"$3 }' |
sort -rn`
---------
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: Dylan Conway <dylan.conway567@gmail.com>
Co-authored-by: Meghan Denny <meghan@bun.com>
Co-authored-by: tayor.fish <contact@taylor.fish>
974 lines
36 KiB
Zig
974 lines
36 KiB
Zig
const Options = struct {
|
|
check_length: CheckLength = .assume_always_less_than_max_path,
|
|
sep: PathSeparators = .any,
|
|
kind: Kind = .any,
|
|
buf_type: BufType = .pool,
|
|
unit: Unit = .u8,
|
|
|
|
const Unit = enum {
|
|
u8,
|
|
u16,
|
|
os,
|
|
};
|
|
|
|
const BufType = enum {
|
|
pool,
|
|
// stack,
|
|
// array_list,
|
|
};
|
|
|
|
const Kind = enum {
|
|
abs,
|
|
rel,
|
|
|
|
// not recommended, but useful when you don't know
|
|
any,
|
|
};
|
|
|
|
const CheckLength = enum {
|
|
assume_always_less_than_max_path,
|
|
check_for_greater_than_max_path,
|
|
};
|
|
|
|
const PathSeparators = enum {
|
|
any,
|
|
auto,
|
|
posix,
|
|
windows,
|
|
|
|
pub fn char(comptime sep: @This()) u8 {
|
|
return switch (sep) {
|
|
.any => @compileError("use the existing slash"),
|
|
.auto => std.fs.path.sep,
|
|
.posix => std.fs.path.sep_posix,
|
|
.windows => std.fs.path.sep_windows,
|
|
};
|
|
}
|
|
};
|
|
|
|
pub fn pathUnit(comptime opts: @This()) type {
|
|
return switch (opts.unit) {
|
|
.u8 => u8,
|
|
.u16 => u16,
|
|
.os => if (Environment.isWindows) u16 else u8,
|
|
};
|
|
}
|
|
|
|
pub fn notPathUnit(comptime opts: @This()) type {
|
|
return switch (opts.unit) {
|
|
.u8 => u16,
|
|
.u16 => u8,
|
|
.os => if (Environment.isWindows) u8 else u16,
|
|
};
|
|
}
|
|
|
|
pub fn maxPathLength(comptime opts: @This()) usize {
|
|
switch (comptime opts.check_length) {
|
|
.assume_always_less_than_max_path => @compileError("max path length is not needed"),
|
|
.check_for_greater_than_max_path => {
|
|
return switch (comptime opts.unit) {
|
|
.u8 => bun.MAX_PATH_BYTES,
|
|
.u16 => bun.PATH_MAX_WIDE,
|
|
.os => if (Environment.isWindows) bun.PATH_MAX_WIDE else bun.MAX_PATH_BYTES,
|
|
};
|
|
},
|
|
}
|
|
}
|
|
|
|
pub fn Buf(comptime opts: @This()) type {
|
|
return switch (opts.buf_type) {
|
|
.pool => struct {
|
|
pooled: switch (opts.unit) {
|
|
.u8 => *PathBuffer,
|
|
.u16 => *WPathBuffer,
|
|
.os => if (Environment.isWindows) *WPathBuffer else *PathBuffer,
|
|
},
|
|
len: usize,
|
|
|
|
pub fn setLength(this: *@This(), new_len: usize) void {
|
|
this.len = new_len;
|
|
}
|
|
|
|
pub fn append(this: *@This(), characters: anytype, add_separator: bool) void {
|
|
if (add_separator) {
|
|
switch (comptime opts.sep) {
|
|
.any, .auto => this.pooled[this.len] = std.fs.path.sep,
|
|
.posix => this.pooled[this.len] = std.fs.path.sep_posix,
|
|
.windows => this.pooled[this.len] = std.fs.path.sep_windows,
|
|
}
|
|
this.len += 1;
|
|
}
|
|
|
|
if (opts.inputChildType(@TypeOf(characters)) == opts.pathUnit()) {
|
|
switch (comptime opts.sep) {
|
|
.any => {
|
|
@memcpy(this.pooled[this.len..][0..characters.len], characters);
|
|
this.len += characters.len;
|
|
},
|
|
.auto, .posix, .windows => {
|
|
for (characters) |c| {
|
|
switch (c) {
|
|
'/', '\\' => this.pooled[this.len] = opts.sep.char(),
|
|
else => this.pooled[this.len] = c,
|
|
}
|
|
this.len += 1;
|
|
}
|
|
},
|
|
}
|
|
} else {
|
|
switch (opts.inputChildType(@TypeOf(characters))) {
|
|
u8 => {
|
|
const converted = bun.strings.convertUTF8toUTF16InBuffer(this.pooled[this.len..], characters);
|
|
if (comptime opts.sep != .any) {
|
|
for (this.pooled[this.len..][0..converted.len], 0..) |c, off| {
|
|
switch (c) {
|
|
'/', '\\' => this.pooled[this.len + off] = opts.sep.char(),
|
|
else => {},
|
|
}
|
|
}
|
|
}
|
|
this.len += converted.len;
|
|
},
|
|
u16 => {
|
|
const converted = bun.strings.convertUTF16toUTF8InBuffer(this.pooled[this.len..], characters) catch unreachable;
|
|
if (comptime opts.sep != .any) {
|
|
for (this.pooled[this.len..][0..converted.len], 0..) |c, off| {
|
|
switch (c) {
|
|
'/', '\\' => this.pooled[this.len + off] = opts.sep.char(),
|
|
else => {},
|
|
}
|
|
}
|
|
}
|
|
this.len += converted.len;
|
|
},
|
|
else => @compileError("unexpected character type"),
|
|
}
|
|
}
|
|
|
|
// switch (@TypeOf(characters)) {
|
|
// []u8, []const u8, [:0]u8, [:0]const u8 => {
|
|
// if (opts.unit == .u8) {
|
|
// this.appendT()
|
|
// }
|
|
// }
|
|
// }
|
|
}
|
|
|
|
// fn append(this: *@This(), characters: []const opts.pathUnit(), add_separator: bool) void {
|
|
// if (add_separator) {}
|
|
// switch (comptime opts.sep) {
|
|
// .any => {
|
|
// @memcpy(this.pooled[this.len..][0..characters.len], characters);
|
|
// this.len += characters.len;
|
|
// },
|
|
// .auto, .posix, .windows => {
|
|
// for (characters) |c| {
|
|
// switch (c) {
|
|
// '/', '\\' => this.pooled[this.len] = opts.sep.char(),
|
|
// else => this.pooled[this.len] = c,
|
|
// }
|
|
// this.len += 1;
|
|
// }
|
|
// },
|
|
// }
|
|
// }
|
|
|
|
fn convertAppend(this: *@This(), characters: []const opts.notPathUnit()) void {
|
|
_ = this;
|
|
_ = characters;
|
|
// switch (comptime opts.sep) {
|
|
// .any => {
|
|
// switch (opts.notPathUnit()) {
|
|
// .u8 => {
|
|
// const converted = bun.strings.convertUTF8toUTF16InBuffer(this.pooled[this.len..], characters);
|
|
// },
|
|
// }
|
|
// },
|
|
// }
|
|
}
|
|
},
|
|
// .stack => struct {
|
|
// buf: PathBuffer,
|
|
// len: u16,
|
|
// },
|
|
// .array_list => struct {
|
|
// list: std.array_list.Managed(opts.pathUnit()),
|
|
// },
|
|
|
|
};
|
|
}
|
|
|
|
const Error = error{MaxPathExceeded};
|
|
|
|
pub fn ResultFn(comptime opts: @This()) fn (comptime T: type) type {
|
|
return struct {
|
|
pub fn Result(comptime T: type) type {
|
|
return switch (opts.check_length) {
|
|
.assume_always_less_than_max_path => T,
|
|
.check_for_greater_than_max_path => Error!T,
|
|
};
|
|
}
|
|
}.Result;
|
|
}
|
|
|
|
pub fn inputChildType(comptime opts: @This(), comptime InputType: type) type {
|
|
_ = opts;
|
|
return switch (@typeInfo(std.meta.Child(InputType))) {
|
|
// handle string literals
|
|
.array => |array| array.child,
|
|
else => std.meta.Child(InputType),
|
|
};
|
|
}
|
|
};
|
|
|
|
pub fn AbsPath(comptime opts: Options) type {
|
|
var copy = opts;
|
|
copy.kind = .abs;
|
|
return Path(copy);
|
|
}
|
|
|
|
pub const AutoAbsPath = Path(.{ .kind = .abs, .sep = .auto });
|
|
|
|
pub fn RelPath(comptime opts: Options) type {
|
|
var copy = opts;
|
|
copy.kind = .rel;
|
|
return Path(copy);
|
|
}
|
|
|
|
pub const AutoRelPath = Path(.{ .kind = .rel, .sep = .auto });
|
|
|
|
pub fn Path(comptime opts: Options) type {
|
|
const Result = opts.ResultFn();
|
|
|
|
// if (opts.unit == .u16 and !Environment.isWindows) {
|
|
// @compileError("utf16 not supported");
|
|
// }
|
|
|
|
// const log = Output.scoped(.Path, .visible);
|
|
|
|
return struct {
|
|
_buf: opts.Buf(),
|
|
|
|
pub fn init() @This() {
|
|
switch (comptime opts.buf_type) {
|
|
.pool => {
|
|
return .{
|
|
._buf = .{
|
|
.pooled = switch (opts.unit) {
|
|
.u8 => bun.path_buffer_pool.get(),
|
|
.u16 => bun.w_path_buffer_pool.get(),
|
|
.os => if (comptime Environment.isWindows)
|
|
bun.w_path_buffer_pool.get()
|
|
else
|
|
bun.path_buffer_pool.get(),
|
|
},
|
|
.len = 0,
|
|
},
|
|
};
|
|
},
|
|
}
|
|
}
|
|
|
|
pub fn deinit(this: *const @This()) void {
|
|
switch (comptime opts.buf_type) {
|
|
.pool => {
|
|
switch (opts.unit) {
|
|
.u8 => bun.path_buffer_pool.put(this._buf.pooled),
|
|
.u16 => bun.w_path_buffer_pool.put(this._buf.pooled),
|
|
.os => if (comptime Environment.isWindows)
|
|
bun.w_path_buffer_pool.put(this._buf.pooled)
|
|
else
|
|
bun.path_buffer_pool.put(this._buf.pooled),
|
|
}
|
|
},
|
|
}
|
|
@constCast(this).* = undefined;
|
|
}
|
|
|
|
pub fn move(this: *const @This()) @This() {
|
|
const moved = this.*;
|
|
@constCast(this).* = undefined;
|
|
return moved;
|
|
}
|
|
|
|
pub fn initTopLevelDir() @This() {
|
|
bun.debugAssert(bun.fs.FileSystem.instance_loaded);
|
|
const top_level_dir = bun.fs.FileSystem.instance.top_level_dir;
|
|
|
|
const trimmed = switch (comptime opts.kind) {
|
|
.abs => trimmed: {
|
|
bun.debugAssert(isInputAbsolute(top_level_dir));
|
|
break :trimmed trimInput(.abs, top_level_dir);
|
|
},
|
|
.rel => @compileError("cannot create a relative path from top_level_dir"),
|
|
.any => trimInput(.abs, top_level_dir),
|
|
};
|
|
|
|
var this = init();
|
|
this._buf.append(trimmed, false);
|
|
return this;
|
|
}
|
|
|
|
pub fn initTopLevelDirLongPath() @This() {
|
|
bun.debugAssert(bun.fs.FileSystem.instance_loaded);
|
|
const top_level_dir = bun.fs.FileSystem.instance.top_level_dir;
|
|
|
|
const trimmed = switch (comptime opts.kind) {
|
|
.abs => trimmed: {
|
|
bun.debugAssert(isInputAbsolute(top_level_dir));
|
|
break :trimmed trimInput(.abs, top_level_dir);
|
|
},
|
|
.rel => @compileError("cannot create a relative path from top_level_dir"),
|
|
.any => trimInput(.abs, top_level_dir),
|
|
};
|
|
|
|
var this = init();
|
|
|
|
if (comptime Environment.isWindows) {
|
|
switch (comptime opts.unit) {
|
|
.u8 => this._buf.append(bun.windows.long_path_prefix_u8, false),
|
|
.u16 => this._buf.append(bun.windows.long_path_prefix, false),
|
|
.os => if (Environment.isWindows)
|
|
this._buf.append(bun.windows.long_path_prefix, false)
|
|
else
|
|
this._buf.append(bun.windows.long_path_prefix_u8, false),
|
|
}
|
|
}
|
|
|
|
this._buf.append(trimmed, false);
|
|
|
|
return this;
|
|
}
|
|
|
|
pub fn initFdPath(fd: FD) !@This() {
|
|
switch (comptime opts.kind) {
|
|
.abs => {},
|
|
.rel => @compileError("cannot create a relative path from getFdPath"),
|
|
.any => {},
|
|
}
|
|
|
|
var this = init();
|
|
switch (comptime opts.buf_type) {
|
|
.pool => {
|
|
const raw = try fd.getFdPath(this._buf.pooled);
|
|
const trimmed = trimInput(.abs, raw);
|
|
this._buf.len = trimmed.len;
|
|
},
|
|
}
|
|
|
|
return this;
|
|
}
|
|
pub fn fromLongPath(input: anytype) Result(@This()) {
|
|
switch (comptime @TypeOf(input)) {
|
|
[]u8, []const u8, [:0]u8, [:0]const u8 => {},
|
|
[]u16, []const u16, [:0]u16, [:0]const u16 => {},
|
|
else => @compileError("unsupported type: " ++ @typeName(@TypeOf(input))),
|
|
}
|
|
const trimmed = switch (comptime opts.kind) {
|
|
.abs => trimmed: {
|
|
bun.debugAssert(isInputAbsolute(input));
|
|
break :trimmed trimInput(.abs, input);
|
|
},
|
|
.rel => trimmed: {
|
|
bun.debugAssert(!isInputAbsolute(input));
|
|
break :trimmed trimInput(.rel, input);
|
|
},
|
|
.any => trimInput(if (isInputAbsolute(input)) .abs else .rel, input),
|
|
};
|
|
|
|
if (comptime opts.check_length == .check_for_greater_than_max_path) {
|
|
if (trimmed.len >= opts.maxPathLength()) {
|
|
return error.MaxPathExceeded;
|
|
}
|
|
}
|
|
|
|
var this = init();
|
|
if (comptime Environment.isWindows) {
|
|
switch (comptime opts.unit) {
|
|
.u8 => this._buf.append(bun.windows.long_path_prefix_u8, false),
|
|
.u16 => this._buf.append(bun.windows.long_path_prefix, false),
|
|
.os => if (Environment.isWindows)
|
|
this._buf.append(bun.windows.long_path_prefix, false)
|
|
else
|
|
this._buf.append(bun.windows.long_path_prefix_u8, false),
|
|
}
|
|
}
|
|
|
|
this._buf.append(trimmed, false);
|
|
return this;
|
|
}
|
|
pub fn from(input: anytype) Result(@This()) {
|
|
const trimmed = switch (comptime opts.kind) {
|
|
.abs => trimmed: {
|
|
bun.debugAssert(isInputAbsolute(input));
|
|
break :trimmed trimInput(.abs, input);
|
|
},
|
|
.rel => trimmed: {
|
|
bun.debugAssert(!isInputAbsolute(input));
|
|
break :trimmed trimInput(.rel, input);
|
|
},
|
|
.any => trimInput(if (isInputAbsolute(input)) .abs else .rel, input),
|
|
};
|
|
|
|
if (comptime opts.check_length == .check_for_greater_than_max_path) {
|
|
if (trimmed.len >= opts.maxPathLength()) {
|
|
return error.MaxPathExceeded;
|
|
}
|
|
}
|
|
|
|
var this = init();
|
|
this._buf.append(trimmed, false);
|
|
return this;
|
|
}
|
|
|
|
pub fn isAbsolute(this: *const @This()) bool {
|
|
return switch (comptime opts.kind) {
|
|
.abs => @compileError("already known to be absolute"),
|
|
.rel => @compileError("already known to not be absolute"),
|
|
.any => isInputAbsolute(this.slice()),
|
|
};
|
|
}
|
|
|
|
pub fn basename(this: *const @This()) []const opts.pathUnit() {
|
|
return bun.strings.basename(opts.pathUnit(), this.slice());
|
|
}
|
|
|
|
pub fn basenameZ(this: *const @This()) [:0]const opts.pathUnit() {
|
|
const full = this.sliceZ();
|
|
const base = bun.strings.basename(opts.pathUnit(), full);
|
|
return full[full.len - base.len ..][0..base.len :0];
|
|
}
|
|
|
|
pub fn dirname(this: *const @This()) ?[]const opts.pathUnit() {
|
|
return bun.Dirname.dirname(opts.pathUnit(), this.slice());
|
|
}
|
|
|
|
pub fn slice(this: *const @This()) []const opts.pathUnit() {
|
|
switch (comptime opts.buf_type) {
|
|
.pool => return this._buf.pooled[0..this._buf.len],
|
|
}
|
|
}
|
|
|
|
pub fn sliceZ(this: *const @This()) [:0]const opts.pathUnit() {
|
|
switch (comptime opts.buf_type) {
|
|
.pool => {
|
|
this._buf.pooled[this._buf.len] = 0;
|
|
return this._buf.pooled[0..this._buf.len :0];
|
|
},
|
|
}
|
|
}
|
|
|
|
pub fn buf(this: *const @This()) []opts.pathUnit() {
|
|
switch (comptime opts.buf_type) {
|
|
.pool => {
|
|
return this._buf.pooled;
|
|
},
|
|
}
|
|
}
|
|
|
|
pub fn setLength(this: *@This(), new_length: usize) void {
|
|
this._buf.setLength(new_length);
|
|
|
|
const trimmed = switch (comptime opts.kind) {
|
|
.abs => trimInput(.abs, this.slice()),
|
|
.rel => trimInput(.rel, this.slice()),
|
|
.any => trimmed: {
|
|
if (this.isAbsolute()) {
|
|
break :trimmed trimInput(.abs, this.slice());
|
|
}
|
|
|
|
break :trimmed trimInput(.rel, this.slice());
|
|
},
|
|
};
|
|
|
|
this._buf.setLength(trimmed.len);
|
|
}
|
|
|
|
pub fn len(this: *const @This()) usize {
|
|
switch (comptime opts.buf_type) {
|
|
.pool => {
|
|
return this._buf.len;
|
|
},
|
|
}
|
|
}
|
|
|
|
pub fn clone(this: *const @This()) @This() {
|
|
switch (comptime opts.buf_type) {
|
|
.pool => {
|
|
var cloned = init();
|
|
@memcpy(cloned._buf.pooled[0..this._buf.len], this._buf.pooled[0..this._buf.len]);
|
|
cloned._buf.len = this._buf.len;
|
|
return cloned;
|
|
},
|
|
}
|
|
}
|
|
|
|
pub fn clear(this: *@This()) void {
|
|
this._buf.setLength(0);
|
|
}
|
|
|
|
pub fn rootLen(input: anytype) ?usize {
|
|
if (comptime Environment.isWindows) {
|
|
if (input.len > 2 and input[1] == ':' and switch (input[2]) {
|
|
'/', '\\' => true,
|
|
else => false,
|
|
}) {
|
|
const letter = input[0];
|
|
if (('a' <= letter and letter <= 'z') or ('A' <= letter and letter <= 'Z')) {
|
|
// C:\
|
|
return 3;
|
|
}
|
|
}
|
|
|
|
if (input.len > 5 and
|
|
switch (input[0]) {
|
|
'/', '\\' => true,
|
|
else => false,
|
|
} and
|
|
switch (input[1]) {
|
|
'/', '\\' => true,
|
|
else => false,
|
|
} and
|
|
switch (input[2]) {
|
|
'\\', '.' => false,
|
|
else => true,
|
|
})
|
|
{
|
|
var i: usize = 3;
|
|
// \\network\share\
|
|
// ^
|
|
while (i < input.len and switch (input[i]) {
|
|
'/', '\\' => false,
|
|
else => true,
|
|
}) {
|
|
i += 1;
|
|
}
|
|
|
|
i += 1;
|
|
// \\network\share\
|
|
// ^
|
|
const start = i;
|
|
while (i < input.len and switch (input[i]) {
|
|
'/', '\\' => false,
|
|
else => true,
|
|
}) {
|
|
i += 1;
|
|
}
|
|
|
|
if (start != i and i < input.len and switch (input[i]) {
|
|
'/', '\\' => true,
|
|
else => false,
|
|
}) {
|
|
// \\network\share\
|
|
// ^
|
|
if (i + 1 < input.len) {
|
|
return i + 1;
|
|
}
|
|
return i;
|
|
}
|
|
}
|
|
|
|
if (input.len > 0 and switch (input[0]) {
|
|
'/', '\\' => true,
|
|
else => false,
|
|
}) {
|
|
// \
|
|
return 1;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
if (input.len > 0 and input[0] == '/') {
|
|
// /
|
|
return 1;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
const TrimInputKind = enum {
|
|
abs,
|
|
rel,
|
|
};
|
|
|
|
fn trimInput(kind: TrimInputKind, input: anytype) []const opts.inputChildType(@TypeOf(input)) {
|
|
var trimmed: []const opts.inputChildType(@TypeOf(input)) = input[0..];
|
|
|
|
if (comptime Environment.isWindows) {
|
|
switch (kind) {
|
|
.abs => {
|
|
const root_len = rootLen(input) orelse 0;
|
|
while (trimmed.len > root_len and switch (trimmed[trimmed.len - 1]) {
|
|
'/', '\\' => true,
|
|
else => false,
|
|
}) {
|
|
trimmed = trimmed[0 .. trimmed.len - 1];
|
|
}
|
|
},
|
|
.rel => {
|
|
if (trimmed.len > 1 and trimmed[0] == '.') {
|
|
const c = trimmed[1];
|
|
if (c == '/' or c == '\\') {
|
|
trimmed = trimmed[2..];
|
|
}
|
|
}
|
|
while (trimmed.len > 0 and switch (trimmed[0]) {
|
|
'/', '\\' => true,
|
|
else => false,
|
|
}) {
|
|
trimmed = trimmed[1..];
|
|
}
|
|
while (trimmed.len > 0 and switch (trimmed[trimmed.len - 1]) {
|
|
'/', '\\' => true,
|
|
else => false,
|
|
}) {
|
|
trimmed = trimmed[0 .. trimmed.len - 1];
|
|
}
|
|
},
|
|
}
|
|
|
|
return trimmed;
|
|
}
|
|
|
|
switch (kind) {
|
|
.abs => {
|
|
const root_len = rootLen(input) orelse 0;
|
|
while (trimmed.len > root_len and trimmed[trimmed.len - 1] == '/') {
|
|
trimmed = trimmed[0 .. trimmed.len - 1];
|
|
}
|
|
},
|
|
.rel => {
|
|
if (trimmed.len > 1 and trimmed[0] == '.' and trimmed[1] == '/') {
|
|
trimmed = trimmed[2..];
|
|
}
|
|
while (trimmed.len > 0 and trimmed[0] == '/') {
|
|
trimmed = trimmed[1..];
|
|
}
|
|
|
|
while (trimmed.len > 0 and trimmed[trimmed.len - 1] == '/') {
|
|
trimmed = trimmed[0 .. trimmed.len - 1];
|
|
}
|
|
},
|
|
}
|
|
|
|
return trimmed;
|
|
}
|
|
|
|
fn isInputAbsolute(input: anytype) bool {
|
|
if (input.len == 0) {
|
|
return false;
|
|
}
|
|
|
|
if (input[0] == '/') {
|
|
return true;
|
|
}
|
|
|
|
if (comptime Environment.isWindows) {
|
|
if (input[0] == '\\') {
|
|
return true;
|
|
}
|
|
|
|
if (input.len < 3) {
|
|
return false;
|
|
}
|
|
|
|
if (input[1] == ':' and switch (input[2]) {
|
|
'/', '\\' => true,
|
|
else => false,
|
|
}) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
pub fn append(this: *@This(), input: anytype) Result(void) {
|
|
const needs_sep = this.len() > 0 and switch (comptime opts.sep) {
|
|
.any => switch (this.slice()[this.len() - 1]) {
|
|
'/', '\\' => false,
|
|
else => true,
|
|
},
|
|
else => this.slice()[this.len() - 1] != opts.sep.char(),
|
|
};
|
|
|
|
switch (comptime opts.kind) {
|
|
.abs => {
|
|
const has_root = this.len() > 0;
|
|
|
|
if (comptime Environment.isDebug) {
|
|
if (has_root) {
|
|
bun.debugAssert(!isInputAbsolute(input));
|
|
} else {
|
|
bun.debugAssert(isInputAbsolute(input));
|
|
}
|
|
}
|
|
|
|
const trimmed = trimInput(if (has_root) .rel else .abs, input);
|
|
|
|
if (trimmed.len == 0) {
|
|
return;
|
|
}
|
|
|
|
if (comptime opts.check_length == .check_for_greater_than_max_path) {
|
|
if (this.len() + trimmed.len + @intFromBool(needs_sep) >= opts.maxPathLength()) {
|
|
return error.MaxPathExceeded;
|
|
}
|
|
}
|
|
|
|
this._buf.append(trimmed, needs_sep);
|
|
},
|
|
.rel => {
|
|
bun.debugAssert(!isInputAbsolute(input));
|
|
|
|
const trimmed = trimInput(.rel, input);
|
|
|
|
if (trimmed.len == 0) {
|
|
return;
|
|
}
|
|
|
|
if (comptime opts.check_length == .check_for_greater_than_max_path) {
|
|
if (this.len() + trimmed.len + @intFromBool(needs_sep) >= opts.maxPathLength()) {
|
|
return error.MaxPathExceeded;
|
|
}
|
|
}
|
|
|
|
this._buf.append(trimmed, needs_sep);
|
|
},
|
|
.any => {
|
|
const input_is_absolute = isInputAbsolute(input);
|
|
|
|
if (comptime Environment.isDebug) {
|
|
if (needs_sep) {
|
|
bun.debugAssert(!input_is_absolute);
|
|
}
|
|
}
|
|
|
|
const trimmed = trimInput(if (this.len() > 0)
|
|
// anything appended to an existing path should be trimmed
|
|
// as a relative path
|
|
.rel
|
|
else if (isInputAbsolute(input))
|
|
// path is empty, trim based on input
|
|
.abs
|
|
else
|
|
.rel, input);
|
|
|
|
if (trimmed.len == 0) {
|
|
return;
|
|
}
|
|
|
|
if (comptime opts.check_length == .check_for_greater_than_max_path) {
|
|
if (this.len() + trimmed.len + @intFromBool(needs_sep) >= opts.maxPathLength()) {
|
|
return error.MaxPathExceeded;
|
|
}
|
|
}
|
|
|
|
this._buf.append(trimmed, needs_sep);
|
|
},
|
|
}
|
|
}
|
|
|
|
pub fn appendFmt(this: *@This(), comptime fmt: []const u8, args: anytype) Result(void) {
|
|
// TODO: there's probably a better way to do this. needed for trimming slashes
|
|
var temp: Path(.{ .buf_type = .pool }) = .init();
|
|
defer temp.deinit();
|
|
|
|
const input = switch (comptime opts.buf_type) {
|
|
.pool => std.fmt.bufPrint(temp._buf.pooled, fmt, args) catch {
|
|
if (comptime opts.check_length == .check_for_greater_than_max_path) {
|
|
return error.MaxPathExceeded;
|
|
}
|
|
unreachable;
|
|
},
|
|
};
|
|
|
|
return this.append(input);
|
|
}
|
|
|
|
pub fn join(this: *@This(), parts: []const []const opts.pathUnit()) Result(void) {
|
|
switch (comptime opts.unit) {
|
|
.u8 => {},
|
|
.u16 => @compileError("unsupported unit type"),
|
|
.os => if (Environment.isWindows) @compileError("unsupported unit type"),
|
|
}
|
|
|
|
switch (comptime opts.kind) {
|
|
.abs => {},
|
|
.rel => @compileError("cannot join with relative path"),
|
|
.any => {
|
|
bun.debugAssert(this.isAbsolute());
|
|
},
|
|
}
|
|
|
|
const cloned = this.clone();
|
|
defer cloned.deinit();
|
|
|
|
switch (comptime opts.buf_type) {
|
|
.pool => {
|
|
const joined = bun.path.joinAbsStringBuf(
|
|
cloned.slice(),
|
|
this._buf.pooled,
|
|
parts,
|
|
switch (opts.sep) {
|
|
.any, .auto => .auto,
|
|
.posix => .posix,
|
|
.windows => .windows,
|
|
},
|
|
);
|
|
|
|
const trimmed = trimInput(.abs, joined);
|
|
this._buf.len = trimmed.len;
|
|
},
|
|
}
|
|
}
|
|
|
|
pub fn appendJoin(this: *@This(), part: anytype) Result(void) {
|
|
switch (comptime opts.kind) {
|
|
.abs => {},
|
|
.rel => @compileError("cannot join with relative path"),
|
|
.any => {
|
|
bun.debugAssert(this.isAbsolute());
|
|
},
|
|
}
|
|
|
|
switch (comptime @TypeOf(part)) {
|
|
[]u8, []const u8 => {
|
|
switch (comptime opts.pathUnit()) {
|
|
u8 => {
|
|
const cwd_path_buf = bun.path_buffer_pool.get();
|
|
defer bun.path_buffer_pool.put(cwd_path_buf);
|
|
const current_slice = this.slice();
|
|
const cwd_path = cwd_path_buf[0..current_slice.len];
|
|
bun.copy(u8, cwd_path, current_slice);
|
|
|
|
const joined = bun.path.joinStringBuf(
|
|
this._buf.pooled,
|
|
&[_][]const u8{ cwd_path, part },
|
|
switch (opts.sep) {
|
|
.any, .auto => .auto,
|
|
.posix => .posix,
|
|
.windows => .windows,
|
|
},
|
|
);
|
|
|
|
const trimmed = trimInput(.abs, joined);
|
|
this._buf.len = trimmed.len;
|
|
},
|
|
u16 => {
|
|
const path_buf = bun.w_path_buffer_pool.get();
|
|
defer bun.w_path_buffer_pool.put(path_buf);
|
|
const converted = bun.strings.convertUTF8toUTF16InBuffer(path_buf, part);
|
|
return this.appendJoin(converted);
|
|
},
|
|
else => @compileError("unsupported unit type"),
|
|
}
|
|
},
|
|
[]u16, []const u16 => {
|
|
switch (comptime opts.pathUnit()) {
|
|
u16 => {
|
|
const cwd_path_buf = bun.w_path_buffer_pool.get();
|
|
defer bun.w_path_buffer_pool.put(cwd_path_buf);
|
|
const current_slice = this.slice();
|
|
const cwd_path = cwd_path_buf[0..current_slice.len];
|
|
bun.copy(u16, cwd_path, current_slice);
|
|
|
|
const joined = bun.path.joinStringBufW(
|
|
this._buf.pooled,
|
|
&[_][]const u16{ cwd_path, part },
|
|
switch (opts.sep) {
|
|
.any, .auto => .auto,
|
|
.posix => .posix,
|
|
.windows => .windows,
|
|
},
|
|
);
|
|
|
|
const trimmed = trimInput(.abs, joined);
|
|
this._buf.len = trimmed.len;
|
|
},
|
|
u8 => {
|
|
const path_buf = bun.path_buffer_pool.get();
|
|
defer bun.path_buffer_pool.put(path_buf);
|
|
const converted = bun.strings.convertUTF16toUTF8InBuffer(path_buf, part) catch {
|
|
return .initError(.MaxPathExceeded);
|
|
};
|
|
return this.appendJoin(converted);
|
|
},
|
|
else => @compileError("unsupported unit type"),
|
|
}
|
|
},
|
|
else => @compileError("unsupported type: " ++ @typeName(@TypeOf(part))),
|
|
}
|
|
}
|
|
|
|
pub fn relative(this: *const @This(), to: anytype) RelPath(opts) {
|
|
switch (comptime opts.buf_type) {
|
|
.pool => {
|
|
var output: RelPath(opts) = .init();
|
|
const rel = bun.path.relativeBufZ(output._buf.pooled, this.slice(), to.slice());
|
|
const trimmed = trimInput(.rel, rel);
|
|
output._buf.len = trimmed.len;
|
|
return output;
|
|
},
|
|
}
|
|
}
|
|
|
|
pub fn undo(this: *@This(), n_components: usize) void {
|
|
const min_len = switch (comptime opts.kind) {
|
|
.abs => rootLen(this.slice()) orelse 0,
|
|
.rel => 0,
|
|
.any => min_len: {
|
|
if (this.isAbsolute()) {
|
|
break :min_len rootLen(this.slice()) orelse 0;
|
|
}
|
|
break :min_len 0;
|
|
},
|
|
};
|
|
|
|
var i: usize = 0;
|
|
while (i < n_components) {
|
|
const slash = switch (comptime opts.sep) {
|
|
.any => std.mem.lastIndexOfAny(opts.pathUnit(), this.slice(), &.{ std.fs.path.sep_posix, std.fs.path.sep_windows }),
|
|
.auto => std.mem.lastIndexOfScalar(opts.pathUnit(), this.slice(), std.fs.path.sep),
|
|
.posix => std.mem.lastIndexOfScalar(opts.pathUnit(), this.slice(), std.fs.path.sep_posix),
|
|
.windows => std.mem.lastIndexOfScalar(opts.pathUnit(), this.slice(), std.fs.path.sep_windows),
|
|
} orelse {
|
|
this._buf.setLength(min_len);
|
|
return;
|
|
};
|
|
|
|
if (slash < min_len) {
|
|
this._buf.setLength(min_len);
|
|
return;
|
|
}
|
|
|
|
this._buf.setLength(slash);
|
|
i += 1;
|
|
}
|
|
}
|
|
|
|
const ResetScope = struct {
|
|
path: *Path(opts),
|
|
saved_len: usize,
|
|
|
|
pub fn restore(this: *const ResetScope) void {
|
|
this.path._buf.setLength(this.saved_len);
|
|
}
|
|
};
|
|
|
|
pub fn save(this: *@This()) ResetScope {
|
|
return .{ .path = this, .saved_len = this.len() };
|
|
}
|
|
};
|
|
}
|
|
|
|
const std = @import("std");
|
|
|
|
const bun = @import("bun");
|
|
const Environment = bun.Environment;
|
|
const FD = bun.FD;
|
|
const Output = bun.Output;
|
|
const PathBuffer = bun.PathBuffer;
|
|
const WPathBuffer = bun.WPathBuffer;
|