mirror of
https://github.com/oven-sh/bun
synced 2026-02-10 10:58:56 +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>
345 lines
12 KiB
Zig
345 lines
12 KiB
Zig
unsafe_http_client: AsyncHTTP = undefined,
|
|
response: bun.http.HTTPClientResult = .{},
|
|
task_id: Task.Id,
|
|
url_buf: []const u8 = &[_]u8{},
|
|
retried: u16 = 0,
|
|
allocator: std.mem.Allocator,
|
|
request_buffer: MutableString = undefined,
|
|
response_buffer: MutableString = undefined,
|
|
package_manager: *PackageManager,
|
|
callback: union(Task.Tag) {
|
|
package_manifest: struct {
|
|
loaded_manifest: ?Npm.PackageManifest = null,
|
|
name: strings.StringOrTinyString,
|
|
is_extended_manifest: bool = false,
|
|
},
|
|
extract: ExtractTarball,
|
|
git_clone: void,
|
|
git_checkout: void,
|
|
local_tarball: void,
|
|
},
|
|
/// Key in patchedDependencies in package.json
|
|
apply_patch_task: ?*PatchTask = null,
|
|
next: ?*NetworkTask = null,
|
|
|
|
pub const DedupeMapEntry = struct {
|
|
is_required: bool,
|
|
};
|
|
pub const DedupeMap = std.HashMap(Task.Id, DedupeMapEntry, IdentityContext(Task.Id), 80);
|
|
|
|
pub fn notify(this: *NetworkTask, async_http: *AsyncHTTP, result: bun.http.HTTPClientResult) void {
|
|
defer this.package_manager.wake();
|
|
async_http.real.?.* = async_http.*;
|
|
async_http.real.?.response_buffer = async_http.response_buffer;
|
|
this.response = result;
|
|
this.package_manager.async_network_task_queue.push(this);
|
|
}
|
|
|
|
pub const Authorization = enum {
|
|
no_authorization,
|
|
allow_authorization,
|
|
};
|
|
|
|
// We must use a less restrictive Accept header value
|
|
// https://github.com/oven-sh/bun/issues/341
|
|
// https://www.jfrog.com/jira/browse/RTFACT-18398
|
|
const accept_header_value = "application/vnd.npm.install-v1+json; q=1.0, application/json; q=0.8, */*";
|
|
const accept_header_value_extended = "application/json, */*";
|
|
|
|
const default_headers_buf: string = "Accept" ++ accept_header_value;
|
|
const extended_headers_buf: string = "Accept" ++ accept_header_value_extended;
|
|
|
|
fn appendAuth(header_builder: *HeaderBuilder, scope: *const Npm.Registry.Scope) void {
|
|
if (scope.token.len > 0) {
|
|
header_builder.appendFmt("Authorization", "Bearer {s}", .{scope.token});
|
|
} else if (scope.auth.len > 0) {
|
|
header_builder.appendFmt("Authorization", "Basic {s}", .{scope.auth});
|
|
} else {
|
|
return;
|
|
}
|
|
header_builder.append("npm-auth-type", "legacy");
|
|
}
|
|
|
|
fn countAuth(header_builder: *HeaderBuilder, scope: *const Npm.Registry.Scope) void {
|
|
if (scope.token.len > 0) {
|
|
header_builder.count("Authorization", "");
|
|
header_builder.content.cap += "Bearer ".len + scope.token.len;
|
|
} else if (scope.auth.len > 0) {
|
|
header_builder.count("Authorization", "");
|
|
header_builder.content.cap += "Basic ".len + scope.auth.len;
|
|
} else {
|
|
return;
|
|
}
|
|
header_builder.count("npm-auth-type", "legacy");
|
|
}
|
|
|
|
const ForManifestError = OOM || error{
|
|
InvalidURL,
|
|
};
|
|
|
|
pub fn forManifest(
|
|
this: *NetworkTask,
|
|
name: string,
|
|
allocator: std.mem.Allocator,
|
|
scope: *const Npm.Registry.Scope,
|
|
loaded_manifest: ?*const Npm.PackageManifest,
|
|
is_optional: bool,
|
|
needs_extended: bool,
|
|
) ForManifestError!void {
|
|
this.url_buf = blk: {
|
|
|
|
// Not all registries support scoped package names when fetching the manifest.
|
|
// registry.npmjs.org supports both "@storybook%2Faddons" and "@storybook/addons"
|
|
// Other registries like AWS codeartifact only support the former.
|
|
// "npm" CLI requests the manifest with the encoded name.
|
|
var arena = std.heap.ArenaAllocator.init(bun.default_allocator);
|
|
defer arena.deinit();
|
|
var stack_fallback_allocator = std.heap.stackFallback(512, arena.allocator());
|
|
var encoded_name = name;
|
|
if (strings.containsChar(name, '/')) {
|
|
encoded_name = try std.mem.replaceOwned(u8, stack_fallback_allocator.get(), name, "/", "%2f");
|
|
}
|
|
|
|
const tmp = bun.jsc.URL.join(
|
|
bun.String.borrowUTF8(scope.url.href),
|
|
bun.String.borrowUTF8(encoded_name),
|
|
);
|
|
defer tmp.deref();
|
|
|
|
if (tmp.tag == .Dead) {
|
|
if (!is_optional) {
|
|
this.package_manager.log.addErrorFmt(
|
|
null,
|
|
logger.Loc.Empty,
|
|
allocator,
|
|
"Failed to join registry {f} and package {f} URLs",
|
|
.{ bun.fmt.QuotedFormatter{ .text = scope.url.href }, bun.fmt.QuotedFormatter{ .text = name } },
|
|
) catch |err| bun.handleOom(err);
|
|
} else {
|
|
this.package_manager.log.addWarningFmt(
|
|
null,
|
|
logger.Loc.Empty,
|
|
allocator,
|
|
"Failed to join registry {f} and package {f} URLs",
|
|
.{ bun.fmt.QuotedFormatter{ .text = scope.url.href }, bun.fmt.QuotedFormatter{ .text = name } },
|
|
) catch |err| bun.handleOom(err);
|
|
}
|
|
return error.InvalidURL;
|
|
}
|
|
|
|
if (!(tmp.hasPrefixComptime("https://") or tmp.hasPrefixComptime("http://"))) {
|
|
if (!is_optional) {
|
|
this.package_manager.log.addErrorFmt(
|
|
null,
|
|
logger.Loc.Empty,
|
|
allocator,
|
|
"Registry URL must be http:// or https://\nReceived: \"{f}\"",
|
|
.{tmp},
|
|
) catch |err| bun.handleOom(err);
|
|
} else {
|
|
this.package_manager.log.addWarningFmt(
|
|
null,
|
|
logger.Loc.Empty,
|
|
allocator,
|
|
"Registry URL must be http:// or https://\nReceived: \"{f}\"",
|
|
.{tmp},
|
|
) catch |err| bun.handleOom(err);
|
|
}
|
|
return error.InvalidURL;
|
|
}
|
|
|
|
// This actually duplicates the string! So we defer deref the WTF managed one above.
|
|
break :blk try tmp.toOwnedSlice(allocator);
|
|
};
|
|
|
|
var last_modified: string = "";
|
|
var etag: string = "";
|
|
if (loaded_manifest) |manifest| {
|
|
if ((needs_extended and manifest.pkg.has_extended_manifest) or !needs_extended) {
|
|
last_modified = manifest.pkg.last_modified.slice(manifest.string_buf);
|
|
etag = manifest.pkg.etag.slice(manifest.string_buf);
|
|
}
|
|
}
|
|
|
|
var header_builder = HeaderBuilder{};
|
|
|
|
countAuth(&header_builder, scope);
|
|
|
|
if (etag.len != 0) {
|
|
header_builder.count("If-None-Match", etag);
|
|
}
|
|
|
|
if (last_modified.len != 0) {
|
|
header_builder.count("If-Modified-Since", last_modified);
|
|
}
|
|
|
|
if (header_builder.header_count > 0) {
|
|
const accept_header = if (needs_extended) accept_header_value_extended else accept_header_value;
|
|
header_builder.count("Accept", accept_header);
|
|
if (last_modified.len > 0 and etag.len > 0) {
|
|
header_builder.content.count(last_modified);
|
|
}
|
|
try header_builder.allocate(allocator);
|
|
|
|
appendAuth(&header_builder, scope);
|
|
|
|
if (etag.len != 0) {
|
|
header_builder.append("If-None-Match", etag);
|
|
} else if (last_modified.len != 0) {
|
|
header_builder.append("If-Modified-Since", last_modified);
|
|
}
|
|
|
|
header_builder.append("Accept", accept_header);
|
|
|
|
if (last_modified.len > 0 and etag.len > 0) {
|
|
last_modified = header_builder.content.append(last_modified);
|
|
}
|
|
} else {
|
|
const header_buf = if (needs_extended) &extended_headers_buf else &default_headers_buf;
|
|
try header_builder.entries.append(
|
|
allocator,
|
|
.{
|
|
.name = .{ .offset = 0, .length = @as(u32, @truncate("Accept".len)) },
|
|
.value = .{ .offset = "Accept".len, .length = @as(u32, @truncate(header_buf.len - "Accept".len)) },
|
|
},
|
|
);
|
|
header_builder.header_count = 1;
|
|
header_builder.content = GlobalStringBuilder{ .ptr = @as([*]u8, @ptrCast(@constCast(header_buf.ptr))), .len = header_buf.len, .cap = header_buf.len };
|
|
}
|
|
|
|
this.response_buffer = try MutableString.init(allocator, 0);
|
|
this.allocator = allocator;
|
|
|
|
const url = URL.parse(this.url_buf);
|
|
this.unsafe_http_client = AsyncHTTP.init(allocator, .GET, url, header_builder.entries, header_builder.content.ptr.?[0..header_builder.content.len], &this.response_buffer, "", this.getCompletionCallback(), HTTP.FetchRedirect.follow, .{
|
|
.http_proxy = this.package_manager.httpProxy(url),
|
|
});
|
|
this.unsafe_http_client.client.flags.reject_unauthorized = this.package_manager.tlsRejectUnauthorized();
|
|
|
|
if (PackageManager.verbose_install) {
|
|
this.unsafe_http_client.client.verbose = .headers;
|
|
}
|
|
|
|
this.callback = .{
|
|
.package_manifest = .{
|
|
.name = try strings.StringOrTinyString.initAppendIfNeeded(name, *FileSystem.FilenameStore, FileSystem.FilenameStore.instance),
|
|
.loaded_manifest = if (loaded_manifest) |manifest| manifest.* else null,
|
|
.is_extended_manifest = needs_extended,
|
|
},
|
|
};
|
|
|
|
if (PackageManager.verbose_install) {
|
|
this.unsafe_http_client.verbose = .headers;
|
|
this.unsafe_http_client.client.verbose = .headers;
|
|
}
|
|
|
|
// Incase the ETag causes invalidation, we fallback to the last modified date.
|
|
if (last_modified.len != 0 and bun.feature_flag.BUN_FEATURE_FLAG_LAST_MODIFIED_PRETEND_304.get()) {
|
|
this.unsafe_http_client.client.flags.force_last_modified = true;
|
|
this.unsafe_http_client.client.if_modified_since = last_modified;
|
|
}
|
|
}
|
|
|
|
pub fn getCompletionCallback(this: *NetworkTask) HTTP.HTTPClientResult.Callback {
|
|
return HTTP.HTTPClientResult.Callback.New(*NetworkTask, notify).init(this);
|
|
}
|
|
|
|
pub fn schedule(this: *NetworkTask, batch: *ThreadPool.Batch) void {
|
|
this.unsafe_http_client.schedule(this.allocator, batch);
|
|
}
|
|
|
|
pub const ForTarballError = OOM || error{
|
|
InvalidURL,
|
|
};
|
|
|
|
pub fn forTarball(
|
|
this: *NetworkTask,
|
|
allocator: std.mem.Allocator,
|
|
tarball_: *const ExtractTarball,
|
|
scope: *const Npm.Registry.Scope,
|
|
authorization: NetworkTask.Authorization,
|
|
) ForTarballError!void {
|
|
this.callback = .{ .extract = tarball_.* };
|
|
const tarball = &this.callback.extract;
|
|
const tarball_url = tarball.url.slice();
|
|
if (tarball_url.len == 0) {
|
|
this.url_buf = try ExtractTarball.buildURL(
|
|
scope.url.href,
|
|
tarball.name,
|
|
tarball.resolution.value.npm.version,
|
|
this.package_manager.lockfile.buffers.string_bytes.items,
|
|
);
|
|
} else {
|
|
this.url_buf = tarball_url;
|
|
}
|
|
|
|
if (!(strings.hasPrefixComptime(this.url_buf, "https://") or strings.hasPrefixComptime(this.url_buf, "http://"))) {
|
|
const msg = .{
|
|
.fmt = "Expected tarball URL to start with https:// or http://, got {f} while fetching package {f}",
|
|
.args = .{ bun.fmt.QuotedFormatter{ .text = this.url_buf }, bun.fmt.QuotedFormatter{ .text = tarball.name.slice() } },
|
|
};
|
|
|
|
try this.package_manager.log.addErrorFmt(null, .{}, allocator, msg.fmt, msg.args);
|
|
return error.InvalidURL;
|
|
}
|
|
|
|
this.response_buffer = MutableString.initEmpty(allocator);
|
|
this.allocator = allocator;
|
|
|
|
var header_builder = HeaderBuilder{};
|
|
var header_buf: string = "";
|
|
|
|
if (authorization == .allow_authorization) {
|
|
countAuth(&header_builder, scope);
|
|
}
|
|
|
|
if (header_builder.header_count > 0) {
|
|
try header_builder.allocate(allocator);
|
|
|
|
if (authorization == .allow_authorization) {
|
|
appendAuth(&header_builder, scope);
|
|
}
|
|
|
|
header_buf = header_builder.content.ptr.?[0..header_builder.content.len];
|
|
}
|
|
|
|
const url = URL.parse(this.url_buf);
|
|
|
|
this.unsafe_http_client = AsyncHTTP.init(allocator, .GET, url, header_builder.entries, header_buf, &this.response_buffer, "", this.getCompletionCallback(), HTTP.FetchRedirect.follow, .{
|
|
.http_proxy = this.package_manager.httpProxy(url),
|
|
});
|
|
this.unsafe_http_client.client.flags.reject_unauthorized = this.package_manager.tlsRejectUnauthorized();
|
|
if (PackageManager.verbose_install) {
|
|
this.unsafe_http_client.client.verbose = .headers;
|
|
}
|
|
}
|
|
|
|
const string = []const u8;
|
|
|
|
const std = @import("std");
|
|
|
|
const install = @import("./install.zig");
|
|
const ExtractTarball = install.ExtractTarball;
|
|
const NetworkTask = install.NetworkTask;
|
|
const Npm = install.Npm;
|
|
const PackageManager = install.PackageManager;
|
|
const PatchTask = install.PatchTask;
|
|
const Task = install.Task;
|
|
|
|
const bun = @import("bun");
|
|
const GlobalStringBuilder = bun.StringBuilder;
|
|
const IdentityContext = bun.IdentityContext;
|
|
const MutableString = bun.MutableString;
|
|
const OOM = bun.OOM;
|
|
const ThreadPool = bun.ThreadPool;
|
|
const URL = bun.URL;
|
|
const logger = bun.logger;
|
|
const strings = bun.strings;
|
|
|
|
const Fs = bun.fs;
|
|
const FileSystem = Fs.FileSystem;
|
|
|
|
const HTTP = bun.http;
|
|
const AsyncHTTP = HTTP.AsyncHTTP;
|
|
const HeaderBuilder = HTTP.HeaderBuilder;
|