aboutsummaryrefslogtreecommitdiff
path: root/src/install
diff options
context:
space:
mode:
Diffstat (limited to 'src/install')
-rw-r--r--src/install/dependency.zig52
-rw-r--r--src/install/extract_tarball.zig3
-rw-r--r--src/install/install.zig1170
-rw-r--r--src/install/lockfile.zig185
-rw-r--r--src/install/npm.zig6
-rw-r--r--src/install/resolvers/folder_resolver.zig53
-rw-r--r--src/install/semver.zig116
7 files changed, 1363 insertions, 222 deletions
diff --git a/src/install/dependency.zig b/src/install/dependency.zig
index f4c3c7173..e97cc92f9 100644
--- a/src/install/dependency.zig
+++ b/src/install/dependency.zig
@@ -6,10 +6,18 @@ const std = @import("std");
const SlicedString = Semver.SlicedString;
const PackageNameHash = @import("./install.zig").PackageNameHash;
const Features = @import("./install.zig").Features;
+const Install = @import("./install.zig");
const logger = @import("../logger.zig");
const Dependency = @This();
const string = @import("../string_types.zig").string;
const strings = @import("../string_immutable.zig");
+const bun = @import("../global.zig");
+
+pub const Pair = struct {
+ resolution_id: Install.PackageID = Install.invalid_package_id,
+ dependency: Dependency = .{},
+ failed: ?anyerror = null,
+};
pub const URI = union(Tag) {
local: String,
@@ -63,19 +71,27 @@ pub fn isLessThan(string_buf: []const u8, lhs: Dependency, rhs: Dependency) bool
return strings.cmpStringsAsc(void{}, lhs_name, rhs_name);
}
+pub fn countWithDifferentBuffers(this: Dependency, name_buf: []const u8, version_buf: []const u8, comptime StringBuilder: type, builder: StringBuilder) void {
+ builder.count(this.name.slice(name_buf));
+ builder.count(this.version.literal.slice(version_buf));
+}
+
pub fn count(this: Dependency, buf: []const u8, comptime StringBuilder: type, builder: StringBuilder) void {
- builder.count(this.name.slice(buf));
- builder.count(this.version.literal.slice(buf));
+ this.countWithDifferentBuffers(buf, buf, StringBuilder, builder);
}
pub fn clone(this: Dependency, buf: []const u8, comptime StringBuilder: type, builder: StringBuilder) !Dependency {
+ return this.cloneWithDifferentBuffers(buf, buf, StringBuilder, builder);
+}
+
+pub fn cloneWithDifferentBuffers(this: Dependency, name_buf: []const u8, version_buf: []const u8, comptime StringBuilder: type, builder: StringBuilder) !Dependency {
const out_slice = builder.lockfile.buffers.string_bytes.items;
- const new_literal = builder.append(String, this.version.literal.slice(buf));
+ const new_literal = builder.append(String, this.version.literal.slice(version_buf));
const sliced = new_literal.sliced(out_slice);
return Dependency{
.name_hash = this.name_hash,
- .name = builder.append(String, this.name.slice(buf)),
+ .name = builder.append(String, this.name.slice(name_buf)),
.version = Dependency.parseWithTag(
builder.lockfile.allocator,
new_literal.slice(out_slice),
@@ -128,6 +144,34 @@ pub const Version = struct {
literal: String = String{},
value: Value = Value{ .uninitialized = void{} },
+ pub fn deinit(this: *Version) void {
+ switch (this.tag) {
+ .npm => {
+ this.value.npm.deinit();
+ },
+ else => {},
+ }
+ }
+
+ pub const @"0.0.0" = Version{
+ .tag = Dependency.Version.Tag.npm,
+ .literal = String.init("0.0.0", "0.0.0"),
+ .value = Value{
+ .npm = Semver.Query.Group{
+ .allocator = bun.default_allocator,
+ .head = .{
+ .head = .{
+ .range = .{
+ .left = .{
+ .op = .gte,
+ },
+ },
+ },
+ },
+ },
+ },
+ };
+
pub const zeroed = Version{};
pub fn clone(
diff --git a/src/install/extract_tarball.zig b/src/install/extract_tarball.zig
index ebf73d1a0..c74c0fb29 100644
--- a/src/install/extract_tarball.zig
+++ b/src/install/extract_tarball.zig
@@ -22,6 +22,7 @@ package_id: PackageID,
skip_verify: bool = false,
integrity: Integrity = Integrity{},
url: string = "",
+package_manager: *PackageManager = &PackageManager.instance,
pub inline fn run(this: ExtractTarball, bytes: []const u8) !string {
if (!this.skip_verify and this.integrity.tag.isSupported()) {
@@ -220,7 +221,7 @@ fn extract(this: *const ExtractTarball, tgz_bytes: []const u8) !string {
Output.flush();
}
}
- var folder_name = PackageManager.instance.cachedNPMPackageFolderNamePrint(&abs_buf2, name, this.resolution.value.npm.version);
+ var folder_name = this.package_manager.cachedNPMPackageFolderNamePrint(&abs_buf2, name, this.resolution.value.npm.version);
if (folder_name.len == 0 or (folder_name.len == 1 and folder_name[0] == '/')) @panic("Tried to delete root and stopped it");
var cache_dir = this.cache_dir;
cache_dir.deleteTree(folder_name) catch {};
diff --git a/src/install/install.zig b/src/install/install.zig
index 683261e50..a30fdb139 100644
--- a/src/install/install.zig
+++ b/src/install/install.zig
@@ -175,6 +175,7 @@ const NetworkTask = struct {
allocator: std.mem.Allocator,
request_buffer: MutableString = undefined,
response_buffer: MutableString = undefined,
+ package_manager: *PackageManager = &PackageManager.instance,
callback: union(Task.Tag) {
package_manifest: struct {
loaded_manifest: ?Npm.PackageManifest = null,
@@ -185,8 +186,8 @@ const NetworkTask = struct {
},
pub fn notify(this: *NetworkTask, _: anytype) void {
- defer PackageManager.instance.wake();
- PackageManager.instance.network_channel.writeItem(this) catch {};
+ defer this.package_manager.wake();
+ this.package_manager.network_channel.writeItem(this) catch {};
}
// We must use a less restrictive Accept header value
@@ -330,7 +331,7 @@ const NetworkTask = struct {
0,
this.getCompletionCallback(),
);
- this.http.max_retry_count = PackageManager.instance.options.max_retry_count;
+ this.http.max_retry_count = this.package_manager.options.max_retry_count;
this.callback = .{
.package_manifest = .{
.name = try strings.StringOrTinyString.initAppendIfNeeded(name, *FileSystem.FilenameStore, &FileSystem.FilenameStore.instance),
@@ -369,7 +370,7 @@ const NetworkTask = struct {
scope.url.href,
tarball.name,
tarball.resolution.value.npm.version,
- PackageManager.instance.lockfile.buffers.string_bytes.items,
+ this.package_manager.lockfile.buffers.string_bytes.items,
);
} else {
this.url_buf = tarball.url;
@@ -412,7 +413,7 @@ const NetworkTask = struct {
0,
this.getCompletionCallback(),
);
- this.http.max_retry_count = PackageManager.instance.options.max_retry_count;
+ this.http.max_retry_count = this.package_manager.options.max_retry_count;
this.callback = .{ .extract = tarball };
}
};
@@ -489,6 +490,7 @@ const Task = struct {
log: logger.Log,
id: u64,
err: ?anyerror = null,
+ package_manager: *PackageManager = &PackageManager.instance,
/// An ID that lets us register a callback without keeping the same pointer around
pub const Id = struct {
@@ -519,11 +521,11 @@ const Task = struct {
var this = @fieldParentPtr(Task, "threadpool_task", task);
- defer PackageManager.instance.wake();
+ defer this.package_manager.wake();
switch (this.tag) {
.package_manifest => {
- var allocator = PackageManager.instance.allocator;
+ var allocator = bun.default_allocator;
const package_manifest = Npm.Registry.getPackageMetadata(
allocator,
this.request.package_manifest.network.http.response.?,
@@ -531,6 +533,7 @@ const Task = struct {
&this.log,
this.request.package_manifest.name.slice(),
this.request.package_manifest.network.callback.package_manifest.loaded_manifest,
+ this.package_manager,
) catch |err| {
if (comptime Environment.isDebug) {
if (@errorReturnTrace()) |trace| {
@@ -539,7 +542,7 @@ const Task = struct {
}
this.err = err;
this.status = Status.fail;
- PackageManager.instance.resolve_tasks.writeItem(this.*) catch unreachable;
+ this.package_manager.resolve_tasks.writeItem(this.*) catch unreachable;
return;
};
@@ -550,7 +553,7 @@ const Task = struct {
.fresh => |manifest| {
this.data = .{ .package_manifest = manifest };
this.status = Status.success;
- PackageManager.instance.resolve_tasks.writeItem(this.*) catch unreachable;
+ this.package_manager.resolve_tasks.writeItem(this.*) catch unreachable;
return;
},
.not_found => {
@@ -558,7 +561,7 @@ const Task = struct {
this.request.package_manifest.name.slice(),
}) catch unreachable;
this.status = Status.fail;
- PackageManager.instance.resolve_tasks.writeItem(this.*) catch unreachable;
+ this.package_manager.resolve_tasks.writeItem(this.*) catch unreachable;
return;
},
}
@@ -576,13 +579,13 @@ const Task = struct {
this.err = err;
this.status = Status.fail;
this.data = .{ .extract = "" };
- PackageManager.instance.resolve_tasks.writeItem(this.*) catch unreachable;
+ this.package_manager.resolve_tasks.writeItem(this.*) catch unreachable;
return;
};
this.data = .{ .extract = result };
this.status = Status.success;
- PackageManager.instance.resolve_tasks.writeItem(this.*) catch unreachable;
+ this.package_manager.resolve_tasks.writeItem(this.*) catch unreachable;
},
.binlink => {},
}
@@ -1396,17 +1399,23 @@ const PackageInstall = struct {
}
};
-const Resolution = @import("./resolution.zig").Resolution;
+pub const Resolution = @import("./resolution.zig").Resolution;
const Progress = std.Progress;
const TaggedPointer = @import("../tagged_pointer.zig");
const TaskCallbackContext = union(Tag) {
dependency: PackageID,
request_id: PackageID,
+ root_dependency: PackageID,
+ root_request_id: PackageID,
node_modules_folder: u32, // Really, this is a file descriptor
+ root_node_modules_folder: u32, // Really, this is a file descriptor
pub const Tag = enum {
dependency,
request_id,
node_modules_folder,
+ root_dependency,
+ root_request_id,
+ root_node_modules_folder,
};
};
@@ -1424,6 +1433,7 @@ pub const CacheLevel = struct {
};
const AsyncIO = @import("io");
const Waker = AsyncIO.Waker;
+
// We can't know all the packages we need until we've downloaded all the packages
// The easy way would be:
// 1. Download all packages, parsing their dependencies and enqueuing all dependencies for resolution
@@ -1436,7 +1446,7 @@ pub const PackageManager = struct {
allocator: std.mem.Allocator,
log: *logger.Log,
resolve_tasks: TaskChannel,
- timestamp: u32 = 0,
+ timestamp_for_manifest_cache_control: u32 = 0,
extracted_count: u32 = 0,
default_features: Features = Features{},
summary: Lockfile.Package.Diff.Summary = Lockfile.Package.Diff.Summary{},
@@ -1448,11 +1458,20 @@ pub const PackageManager = struct {
cpu_count: u32 = 0,
package_json_updates: []UpdateRequest = &[_]UpdateRequest{},
+ // progress bar stuff when not stack allocated
+ root_progress_node: *std.Progress.Node = undefined,
+ root_download_node: std.Progress.Node = undefined,
+
to_remove: []const UpdateRequest = &[_]UpdateRequest{},
root_package_json_file: std.fs.File,
root_dependency_list: Lockfile.DependencySlice = .{},
+ /// Used to make "dependencies" optional in the main package
+ /// Depended on packages have to explicitly list their dependencies
+ dynamic_root_dependencies: ?std.ArrayList(Dependency.Pair) = null,
+ // remote_dependencies: RemoteDependency.List = .{},
+
thread_pool: ThreadPool,
manifests: PackageManifestMap = PackageManifestMap{},
@@ -1479,6 +1498,8 @@ pub const PackageManager = struct {
waiter: Waker = undefined,
wait_count: std.atomic.Atomic(usize) = std.atomic.Atomic(usize).init(0),
+ onWake: WakeHandler = .{},
+
const PreallocatedNetworkTasks = std.BoundedArray(NetworkTask, 1024);
const NetworkTaskQueue = std.HashMapUnmanaged(u64, void, IdentityContext(u64), 80);
const PackageIndex = std.AutoHashMapUnmanaged(u64, *Package);
@@ -1491,7 +1512,35 @@ pub const PackageManager = struct {
80,
);
+ pub const WakeHandler = struct {
+ handler: fn (ctx: *anyopaque, pm: *PackageManager) void = undefined,
+ onDependencyError: fn (ctx: *anyopaque, Dependency, PackageID, anyerror) void = undefined,
+ context: ?*anyopaque = null,
+ };
+
+ pub fn failRootResolution(this: *PackageManager, dependency: Dependency, dependency_id: PackageID, err: anyerror) void {
+ if (this.dynamic_root_dependencies) |*dynamic| {
+ dynamic.items[dependency_id].failed = err;
+ if (this.onWake.context) |ctx| {
+ this.onWake.onDependencyError(
+ ctx,
+ dependency,
+ dependency_id,
+ err,
+ );
+ }
+ } else {
+ // this means a bug
+ bun.unreachablePanic("assignRootResolution: dependency_id: {d} out of bounds", .{dependency_id});
+ }
+ }
+
pub fn wake(this: *PackageManager) void {
+ if (this.onWake.context != null) {
+ this.onWake.handler(this.onWake.context.?, this);
+ return;
+ }
+
_ = this.wait_count.fetchAdd(1, .Monotonic);
this.waiter.wake() catch {};
}
@@ -1501,6 +1550,92 @@ pub const PackageManager = struct {
_ = this.waiter.wait() catch 0;
}
+ const DependencyToEnqueue = union(enum) {
+ pending: PackageID,
+ resolution: struct { package_id: PackageID, resolution: Resolution },
+ not_found: void,
+ failure: anyerror,
+ };
+ pub fn enqueueDependencyToRoot(
+ this: *PackageManager,
+ name: []const u8,
+ version_buf: []const u8,
+ version: Dependency.Version,
+ behavior: Dependency.Behavior,
+ ) DependencyToEnqueue {
+ var root_deps = this.dynamicRootDependencies();
+ const existing: []const Dependency.Pair = root_deps.items;
+ var str_buf = this.lockfile.buffers.string_bytes.items;
+ for (existing) |pair, i| {
+ if (strings.eqlLong(this.lockfile.str(pair.dependency.name), name, true)) {
+ if (pair.dependency.version.eql(version, str_buf, version_buf)) {
+ if (pair.resolution_id != invalid_package_id) {
+ return .{
+ .resolution = .{
+ .resolution = this.lockfile.packages.items(.resolution)[pair.resolution_id],
+ .package_id = pair.resolution_id,
+ },
+ };
+ }
+ return .{ .pending = @truncate(u32, i) };
+ }
+ }
+ }
+
+ var builder = this.lockfile.stringBuilder();
+ const dependency = Dependency{
+ .name = String.init(name, name),
+ .name_hash = String.Builder.stringHash(name),
+ .version = version,
+ .behavior = behavior,
+ };
+ dependency.countWithDifferentBuffers(name, version_buf, @TypeOf(&builder), &builder);
+
+ builder.allocate() catch |err| {
+ return .{ .failure = err };
+ };
+
+ const cloned_dependency = dependency.cloneWithDifferentBuffers(name, version_buf, @TypeOf(&builder), &builder) catch unreachable;
+ builder.clamp();
+ const index = @truncate(u32, root_deps.items.len);
+ root_deps.append(
+ .{
+ .dependency = cloned_dependency,
+ },
+ ) catch unreachable;
+ this.enqueueDependencyWithMainAndSuccessFn(
+ index,
+ cloned_dependency,
+ invalid_package_id,
+ true,
+ assignRootResolution,
+ failRootResolution,
+ ) catch |err| {
+ root_deps.items.len = index;
+ return .{ .failure = err };
+ };
+
+ if (root_deps.items[index].failed) |fail| {
+ root_deps.items.len = index;
+ return .{ .failure = fail };
+ }
+
+ const resolution_id = root_deps.items[index].resolution_id;
+
+ // check if we managed to synchronously resolve the dependency
+ if (resolution_id != invalid_package_id) {
+ this.drainDependencyList();
+ return .{
+ .resolution = .{
+ .resolution = this.lockfile.packages.items(.resolution)[resolution_id],
+ .package_id = resolution_id,
+ },
+ };
+ }
+
+ return .{ .pending = index };
+ }
+
pub fn globalLinkDir(this: *PackageManager) !std.fs.Dir {
return this.global_link_dir orelse brk: {
var global_dir = try Options.openGlobalDir(this.options.explicit_global_directory);
@@ -1533,6 +1668,7 @@ pub const PackageManager = struct {
this.ensurePreinstallStateListCapacity(lockfile.packages.len) catch return;
this.preinstall_state.items[package_id] = value;
}
+
pub fn getPreinstallState(this: *PackageManager, package_id: PackageID, _: *Lockfile) PreinstallState {
if (package_id >= this.preinstall_state.items.len) {
return PreinstallState.unknown;
@@ -1779,6 +1915,146 @@ pub const PackageManager = struct {
return true;
}
+ pub fn pathForCachedNPMPath(
+ this: *PackageManager,
+ buf: *[bun.MAX_PATH_BYTES]u8,
+ package_name: []const u8,
+ npm: Semver.Version,
+ ) ![]u8 {
+ var package_name_version_buf: [bun.MAX_PATH_BYTES]u8 = undefined;
+
+ var subpath = std.fmt.bufPrintZ(
+ &package_name_version_buf,
+ "{s}" ++ std.fs.path.sep_str ++ "{any}",
+ .{
+ package_name,
+ npm.fmt(this.lockfile.buffers.string_bytes.items),
+ },
+ ) catch unreachable;
+ return this.getCacheDirectory().readLink(
+ subpath,
+ buf,
+ ) catch |err| {
+ // if we run into an error, delete the symlink
+ // so that we don't repeatedly try to read it
+ std.os.unlinkat(this.getCacheDirectory().fd, subpath, 0) catch {};
+ return err;
+ };
+ }
+
+ pub fn pathForResolution(
+ this: *PackageManager,
+ package_id: PackageID,
+ resolution: Resolution,
+ buf: *[bun.MAX_PATH_BYTES]u8,
+ ) ![]u8 {
+ // const folder_name = this.cachedNPMPackageFolderName(name, version);
+ switch (resolution.tag) {
+ .npm => {
+ const npm = resolution.value.npm;
+ const package_name_ = this.lockfile.packages.items(.name)[package_id];
+ const package_name = this.lockfile.str(package_name_);
+
+ return this.pathForCachedNPMPath(buf, package_name, npm.version);
+ },
+ else => return "",
+ }
+ }
+
+ pub fn getInstalledVersionsFromDiskCache(this: *PackageManager, tags_buf: *std.ArrayList(u8), package_name: []const u8, allocator: std.mem.Allocator) !std.ArrayList(Semver.Version) {
+ var list = std.ArrayList(Semver.Version).init(allocator);
+ var dir = this.getCacheDirectory().openDir(package_name, .{ .iterate = true }) catch |err| {
+ switch (err) {
+ error.FileNotFound, error.NotDir, error.AccessDenied, error.DeviceBusy => {
+ return list;
+ },
+ else => return err,
+ }
+ };
+ defer dir.close();
+ var iter = dir.iterate();
+
+ while (try iter.next()) |entry| {
+ if (entry.kind != .Directory and entry.kind != .SymLink) continue;
+ const name = entry.name;
+ var sliced = SlicedString.init(name, name);
+ var parsed = Semver.Version.parse(sliced, allocator);
+ if (!parsed.valid or parsed.wildcard != .none) continue;
+ // not handling OOM
+ // TODO: wildcard
+ const total = parsed.version.tag.build.len() + parsed.version.tag.pre.len();
+ if (total > 0) {
+ tags_buf.ensureUnusedCapacity(total) catch unreachable;
+ var available = tags_buf.items.ptr[tags_buf.items.len..tags_buf.capacity];
+ const new_version = parsed.version.cloneInto(name, &available);
+ tags_buf.items.len += total;
+ parsed.version = new_version;
+ }
+
+ list.append(parsed.version) catch unreachable;
+ }
+
+ return list;
+ }
+
+ pub fn resolveFromDiskCache(this: *PackageManager, package_name: []const u8, version: Dependency.Version) ?PackageID {
+ if (version.tag != .npm) {
+ // only npm supported right now
+ // tags are more ambiguous
+ return null;
+ }
+
+ var arena = std.heap.ArenaAllocator.init(this.allocator);
+ defer arena.deinit();
+ var arena_alloc = arena.allocator();
+ var stack_fallback = std.heap.stackFallback(4096, arena_alloc);
+ var allocator = stack_fallback.get();
+ var tags_buf = std.ArrayList(u8).init(allocator);
+ var installed_versions = this.getInstalledVersionsFromDiskCache(&tags_buf, package_name, allocator) catch |err| {
+ Output.debug("error getting installed versions from disk cache: {s}", .{std.mem.span(@errorName(err))});
+ return null;
+ };
+
+ // TODO: make this fewer passes
+ std.sort.sort(
+ Semver.Version,
+ installed_versions.items,
+ @as([]const u8, tags_buf.items),
+ Semver.Version.sortGt,
+ );
+ for (installed_versions.items) |installed_version| {
+ if (version.value.npm.satisfies(installed_version)) {
+ var buf: [bun.MAX_PATH_BYTES]u8 = undefined;
+ var npm_package_path = this.pathForCachedNPMPath(&buf, package_name, installed_version) catch |err| {
+ Output.debug("error getting path for cached npm path: {s}", .{std.mem.span(@errorName(err))});
+ return null;
+ };
+ const dependency = Dependency.Version{
+ .tag = .npm,
+ .value = .{
+ .npm = Semver.Query.Group.from(installed_version),
+ },
+ };
+ switch (FolderResolution.getOrPut(.{ .cache_folder = npm_package_path }, dependency, ".", this)) {
+ .new_package_id => |id| {
+ this.enqueueDependencyList(this.lockfile.packages.items(.dependencies)[id], false);
+ return id;
+ },
+ .package_id => |id| {
+ this.enqueueDependencyList(this.lockfile.packages.items(.dependencies)[id], false);
+ return id;
+ },
+ .err => |err| {
+ Output.debug("error getting or putting folder resolution: {s}", .{std.mem.span(@errorName(err))});
+ return null;
+ },
+ }
+ }
+ }
+
+ return null;
+ }
+
const ResolvedPackageResult = struct {
package: Lockfile.Package,
@@ -1798,6 +2074,7 @@ pub const PackageManager = struct {
behavior: Behavior,
manifest: *const Npm.PackageManifest,
find_result: Npm.PackageManifest.FindResult,
+ comptime successFn: SuccessFn,
) !?ResolvedPackageResult {
// Was this package already allocated? Let's reuse the existing one.
@@ -1814,7 +2091,7 @@ pub const PackageManager = struct {
},
},
)) |id| {
- this.lockfile.buffers.resolutions.items[dependency_id] = id;
+ successFn(this, dependency_id, id);
return ResolvedPackageResult{
.package = this.lockfile.packages.get(id),
.is_first_time = false,
@@ -1836,7 +2113,7 @@ pub const PackageManager = struct {
// appendPackage sets the PackageID on the package
package = try this.lockfile.appendPackage(package);
- if (!behavior.isEnabled(if (this.root_dependency_list.contains(dependency_id))
+ if (!behavior.isEnabled(if (this.isRootDependency(dependency_id))
this.options.local_package_features
else
this.options.remote_package_features))
@@ -1846,9 +2123,8 @@ pub const PackageManager = struct {
const preinstall = this.determinePreinstallState(package, this.lockfile);
- this.lockfile.buffers.resolutions.items[dependency_id] = package.meta.id;
if (comptime Environment.isDebug or Environment.isTest) std.debug.assert(package.meta.id != invalid_package_id);
-
+ defer successFn(this, dependency_id, package.meta.id);
switch (preinstall) {
// Is this package already in the cache?
// We don't need to download the tarball, but we should enqueue dependencies
@@ -1888,6 +2164,7 @@ pub const PackageManager = struct {
.task_id = task_id,
.callback = undefined,
.allocator = this.allocator,
+ .package_manager = this,
};
const scope = this.scopeForPackageName(this.lockfile.str(package.name));
@@ -1934,7 +2211,7 @@ pub const PackageManager = struct {
const manifest: Npm.PackageManifest = manifest_;
loaded_manifest = manifest;
- if (this.options.enable.manifest_cache_control and manifest.pkg.public_max_age > this.timestamp) {
+ if (this.options.enable.manifest_cache_control and manifest.pkg.public_max_age > this.timestamp_for_manifest_cache_control) {
try this.manifests.put(this.allocator, @truncate(PackageNameHash, manifest.pkg.name.hash), manifest);
}
@@ -1947,7 +2224,7 @@ pub const PackageManager = struct {
}
// Was it recent enough to just load it without the network call?
- if (this.options.enable.manifest_cache_control and manifest.pkg.public_max_age > this.timestamp) {
+ if (this.options.enable.manifest_cache_control and manifest.pkg.public_max_age > this.timestamp_for_manifest_cache_control) {
return manifest;
}
}
@@ -1962,6 +2239,7 @@ pub const PackageManager = struct {
.callback = undefined,
.task_id = task_id,
.allocator = this.allocator,
+ .package_manager = this,
};
try network_task.forManifest(name, this.allocator, this.scopeForPackageName(name), loaded_manifest);
this.enqueueNetworkTask(network_task);
@@ -1984,6 +2262,25 @@ pub const PackageManager = struct {
this.network_task_fifo.writeItemAssumeCapacity(task);
}
+ const SuccessFn = fn (*PackageManager, PackageID, PackageID) void;
+ const FailFn = fn (*PackageManager, Dependency, PackageID, anyerror) void;
+ fn assignResolution(this: *PackageManager, dependency_id: PackageID, package_id: PackageID) void {
+ this.lockfile.buffers.resolutions.items[dependency_id] = package_id;
+ }
+
+ fn assignRootResolution(this: *PackageManager, dependency_id: PackageID, package_id: PackageID) void {
+ if (this.dynamic_root_dependencies) |*dynamic| {
+ dynamic.items[dependency_id].resolution_id = package_id;
+ } else {
+ if (this.lockfile.buffers.resolutions.items.len > dependency_id) {
+ this.lockfile.buffers.resolutions.items[dependency_id] = package_id;
+ } else {
+ // this means a bug
+ bun.unreachablePanic("assignRootResolution: dependency_id: {d} out of bounds (package_id: {d})", .{ dependency_id, package_id });
+ }
+ }
+ }
+
pub fn getOrPutResolvedPackage(
this: *PackageManager,
name_hash: PackageNameHash,
@@ -1992,6 +2289,7 @@ pub const PackageManager = struct {
behavior: Behavior,
dependency_id: PackageID,
resolution: PackageID,
+ comptime successFn: SuccessFn,
) !?ResolvedPackageResult {
if (resolution < this.lockfile.packages.len) {
return ResolvedPackageResult{ .package = this.lockfile.packages.get(resolution) };
@@ -2011,7 +2309,17 @@ pub const PackageManager = struct {
else => unreachable,
};
- return try getOrPutResolvedPackageWithFindResult(this, name_hash, name, version, dependency_id, behavior, manifest, find_result);
+ return try getOrPutResolvedPackageWithFindResult(
+ this,
+ name_hash,
+ name,
+ version,
+ dependency_id,
+ behavior,
+ manifest,
+ find_result,
+ successFn,
+ );
},
.folder => {
@@ -2021,12 +2329,12 @@ pub const PackageManager = struct {
switch (res) {
.err => |err| return err,
.package_id => |package_id| {
- this.lockfile.buffers.resolutions.items[dependency_id] = package_id;
+ successFn(this, dependency_id, package_id);
return ResolvedPackageResult{ .package = this.lockfile.packages.get(package_id) };
},
.new_package_id => |package_id| {
- this.lockfile.buffers.resolutions.items[dependency_id] = package_id;
+ successFn(this, dependency_id, package_id);
return ResolvedPackageResult{ .package = this.lockfile.packages.get(package_id), .is_first_time = true };
},
}
@@ -2096,10 +2404,31 @@ pub const PackageManager = struct {
return &task.threadpool_task;
}
- inline fn enqueueDependency(this: *PackageManager, id: u32, dependency: Dependency, resolution: PackageID) !void {
+ pub inline fn enqueueDependency(this: *PackageManager, id: u32, dependency: Dependency, resolution: PackageID) !void {
return try this.enqueueDependencyWithMain(id, dependency, resolution, false);
}
+ pub inline fn enqueueMainDependency(this: *PackageManager, id: u32, dependency: Dependency, resolution: PackageID) !void {
+ return try this.enqueueDependencyWithMain(id, dependency, resolution, true);
+ }
+
+ pub fn dynamicRootDependencies(this: *PackageManager) *std.ArrayList(Dependency.Pair) {
+ if (this.dynamic_root_dependencies == null) {
+ const root_deps = this.lockfile.rootPackage().?.dependencies.get(this.lockfile.buffers.dependencies.items);
+
+ this.dynamic_root_dependencies = std.ArrayList(Dependency.Pair).initCapacity(this.allocator, root_deps.len) catch unreachable;
+ this.dynamic_root_dependencies.?.items.len = root_deps.len;
+ for (root_deps) |dep, i| {
+ this.dynamic_root_dependencies.?.items[i] = .{
+ .dependency = dep,
+ .resolution_id = invalid_package_id,
+ };
+ }
+ }
+
+ return &this.dynamic_root_dependencies.?;
+ }
+
pub fn writeYarnLock(this: *PackageManager) !void {
var printer = Lockfile.Printer{
.lockfile = this.lockfile,
@@ -2142,6 +2471,14 @@ pub const PackageManager = struct {
try tmpfile.promote(tmpname, std.fs.cwd().fd, "yarn.lock");
}
+ pub fn isRootDependency(this: *const PackageManager, id: PackageID) bool {
+ if (this.dynamic_root_dependencies != null) {
+ return false;
+ }
+
+ return this.root_dependency_list.contains(id);
+ }
+
fn enqueueDependencyWithMain(
this: *PackageManager,
id: u32,
@@ -2149,6 +2486,25 @@ pub const PackageManager = struct {
resolution: PackageID,
comptime is_main: bool,
) !void {
+ return this.enqueueDependencyWithMainAndSuccessFn(
+ id,
+ dependency,
+ resolution,
+ is_main,
+ assignResolution,
+ null,
+ );
+ }
+
+ pub fn enqueueDependencyWithMainAndSuccessFn(
+ this: *PackageManager,
+ id: u32,
+ dependency: Dependency,
+ resolution: PackageID,
+ comptime is_main: bool,
+ comptime successFn: SuccessFn,
+ comptime failFn: ?FailFn,
+ ) !void {
const name = dependency.name;
const name_hash = dependency.name_hash;
const version: Dependency.Version = dependency.version;
@@ -2156,7 +2512,7 @@ pub const PackageManager = struct {
if (comptime !is_main) {
// it might really be main
- if (!this.root_dependency_list.contains(id))
+ if (!this.isRootDependency(id))
if (!dependency.behavior.isEnabled(switch (dependency.version.tag) {
.folder => this.options.remote_package_features,
.dist_tag, .npm => this.options.remote_package_features,
@@ -2175,6 +2531,7 @@ pub const PackageManager = struct {
dependency.behavior,
id,
resolution,
+ successFn,
);
retry_with_new_resolve_result: while (true) {
@@ -2182,36 +2539,66 @@ pub const PackageManager = struct {
switch (err) {
error.DistTagNotFound => {
if (dependency.behavior.isRequired()) {
- this.log.addErrorFmt(
- null,
- logger.Loc.Empty,
- this.allocator,
- "package \"{s}\" with tag \"{s}\" not found, but package exists",
- .{
- this.lockfile.str(name),
- this.lockfile.str(version.value.dist_tag),
- },
- ) catch unreachable;
+ if (failFn) |fail| {
+ fail(
+ this,
+ dependency,
+ id,
+ err,
+ );
+ } else {
+ this.log.addErrorFmt(
+ null,
+ logger.Loc.Empty,
+ this.allocator,
+ "package \"{s}\" with tag \"{s}\" not found, but package exists",
+ .{
+ this.lockfile.str(name),
+ this.lockfile.str(version.value.dist_tag),
+ },
+ ) catch unreachable;
+ }
}
return;
},
error.NoMatchingVersion => {
if (dependency.behavior.isRequired()) {
- this.log.addErrorFmt(
- null,
- logger.Loc.Empty,
- this.allocator,
- "No version matching \"{s}\" found for specifier \"{s}\" (but package exists)",
- .{
- this.lockfile.str(version.literal),
- this.lockfile.str(name),
- },
- ) catch unreachable;
+ if (failFn) |fail| {
+ fail(
+ this,
+ dependency,
+ id,
+ err,
+ );
+ } else {
+ this.log.addErrorFmt(
+ null,
+ logger.Loc.Empty,
+ this.allocator,
+ "No version matching \"{s}\" found for specifier \"{s}\" (but package exists)",
+ .{
+ this.lockfile.str(version.literal),
+ this.lockfile.str(name),
+ },
+ ) catch unreachable;
+ }
}
return;
},
- else => return err,
+ else => {
+ if (failFn) |fail| {
+ fail(
+ this,
+ dependency,
+ id,
+ err,
+ );
+ return;
+ }
+
+ return err;
+ },
}
};
@@ -2251,7 +2638,7 @@ pub const PackageManager = struct {
const manifest: Npm.PackageManifest = manifest_;
loaded_manifest = manifest;
- if (this.options.enable.manifest_cache_control and manifest.pkg.public_max_age > this.timestamp) {
+ if (this.options.enable.manifest_cache_control and manifest.pkg.public_max_age > this.timestamp_for_manifest_cache_control) {
try this.manifests.put(this.allocator, @truncate(PackageNameHash, manifest.pkg.name.hash), manifest);
}
@@ -2267,6 +2654,7 @@ pub const PackageManager = struct {
dependency.behavior,
&loaded_manifest.?,
find_result,
+ successFn,
) catch null) |new_resolve_result| {
resolve_result_ = new_resolve_result;
_ = this.network_dedupe_map.remove(task_id);
@@ -2276,7 +2664,7 @@ pub const PackageManager = struct {
}
// Was it recent enough to just load it without the network call?
- if (this.options.enable.manifest_cache_control and manifest.pkg.public_max_age > this.timestamp) {
+ if (this.options.enable.manifest_cache_control and manifest.pkg.public_max_age > this.timestamp_for_manifest_cache_control) {
_ = this.network_dedupe_map.remove(task_id);
continue :retry_from_manifests_ptr;
}
@@ -2309,7 +2697,8 @@ pub const PackageManager = struct {
manifest_entry_parse.value_ptr.* = TaskCallbackList{};
}
- try manifest_entry_parse.value_ptr.append(this.allocator, TaskCallbackContext{ .dependency = id });
+ const callback_tag = comptime if (successFn == assignRootResolution) "root_dependency" else "dependency";
+ try manifest_entry_parse.value_ptr.append(this.allocator, @unionInit(TaskCallbackContext, callback_tag, id));
}
return;
}
@@ -2324,6 +2713,7 @@ pub const PackageManager = struct {
dependency.behavior,
id,
resolution,
+ successFn,
) catch |err| brk: {
if (err == error.MissingPackageJSON) {
break :brk null;
@@ -2401,6 +2791,8 @@ pub const PackageManager = struct {
var lockfile = this.lockfile;
var dependency_queue = &lockfile.scratch.dependency_list_queue;
+ this.flushNetworkQueue();
+
while (dependency_queue.readItem()) |dependencies_list| {
var i: u32 = dependencies_list.off;
const end = dependencies_list.off + dependencies_list.len;
@@ -2459,6 +2851,10 @@ pub const PackageManager = struct {
}
}
+ this.drainDependencyList();
+ }
+
+ pub fn drainDependencyList(this: *PackageManager) void {
// Step 2. If there were cached dependencies, go through all of those but don't download the devDependencies for them.
this.flushDependencyQueue();
@@ -2469,11 +2865,68 @@ pub const PackageManager = struct {
this.pending_tasks += @truncate(u32, count);
this.total_tasks += @truncate(u32, count);
this.network_resolve_batch.push(this.network_tarball_batch);
+
HTTP.http_thread.schedule(this.network_resolve_batch);
this.network_tarball_batch = .{};
this.network_resolve_batch = .{};
}
+ fn processDependencyList(
+ this: *PackageManager,
+ dep_list: TaskCallbackList,
+ comptime Context: type,
+ ctx: Context,
+ comptime callbacks: anytype,
+ ) !void {
+ if (dep_list.items.len > 0) {
+ var dependency_list = dep_list;
+ var any_root = false;
+ for (dependency_list.items) |item| {
+ switch (item) {
+ .dependency => |dependency_id| {
+ const dependency = this.lockfile.buffers.dependencies.items[dependency_id];
+ const resolution = this.lockfile.buffers.resolutions.items[dependency_id];
+
+ try this.enqueueDependency(
+ dependency_id,
+ dependency,
+ resolution,
+ );
+ },
+
+ .root_dependency => |dependency_id| {
+ const pair = this.dynamicRootDependencies().items[dependency_id];
+ const dependency = pair.dependency;
+ const resolution = pair.resolution_id;
+
+ try this.enqueueDependencyWithMainAndSuccessFn(
+ dependency_id,
+ dependency,
+ resolution,
+ true,
+ assignRootResolution,
+ failRootResolution,
+ );
+
+ const new_resolution_id = this.dynamicRootDependencies().items[dependency_id].resolution_id;
+ if (new_resolution_id != pair.resolution_id) {
+ any_root = true;
+ }
+ },
+ else => unreachable,
+ }
+ }
+
+ if (comptime @TypeOf(callbacks.onResolve) != void) {
+ if (any_root) {
+ callbacks.onResolve(ctx);
+ }
+ }
+
+ dependency_list.deinit(this.allocator);
+ }
+ }
+
const CacheDir = struct { path: string, is_node_modules: bool };
pub fn fetchCacheDirectoryPath(
env_loader: *DotEnv.Loader,
@@ -2501,16 +2954,18 @@ pub const PackageManager = struct {
return CacheDir{ .is_node_modules = true, .path = Fs.FileSystem.instance.abs(&fallback_parts) };
}
- fn runTasks(
+ pub fn runTasks(
manager: *PackageManager,
comptime ExtractCompletionContext: type,
extract_ctx: ExtractCompletionContext,
- comptime callback_fn: anytype,
+ comptime callbacks: anytype,
comptime log_level: Options.LogLevel,
) anyerror!void {
var batch = ThreadPool.Batch{};
var has_updated_this_run = false;
+ var timestamp_this_tick: ?u32 = null;
+
while (manager.network_channel.tryReadItem() catch null) |task_| {
var task: *NetworkTask = task_;
manager.pending_tasks -|= 1;
@@ -2526,9 +2981,27 @@ pub const PackageManager = struct {
}
const response = task.http.response orelse {
- if (comptime log_level != .silent) {
+ const err = task.http.err orelse error.HTTPError;
+
+ if (@TypeOf(callbacks.onPackageManifestError) != void) {
+ if (manager.dynamic_root_dependencies) |*root_deps| {
+ var deps: []Dependency.Pair = root_deps.items;
+ for (deps) |*dep| {
+ if (strings.eql(manager.lockfile.str(dep.dependency.name), name.slice())) {
+ dep.failed = dep.failed orelse err;
+ }
+ }
+ }
+
+ callbacks.onPackageManifestError(
+ extract_ctx,
+ name.slice(),
+ err,
+ task.url_buf,
+ );
+ } else if (comptime log_level != .silent) {
const fmt = "\n<r><red>error<r>: {s} downloading package manifest <b>{s}<r>\n";
- const error_name: string = if (task.http.err) |err| std.mem.span(@errorName(err)) else "failed";
+ const error_name: string = std.mem.span(@errorName(err));
const args = .{ error_name, name.slice() };
if (comptime log_level.showProgress()) {
Output.prettyWithPrinterFn(fmt, args, Progress.log, &manager.progress);
@@ -2544,72 +3017,100 @@ pub const PackageManager = struct {
};
if (response.status_code > 399) {
- switch (response.status_code) {
- 404 => {
- if (comptime log_level != .silent) {
- const fmt = "\n<r><red>error<r>: package <b>\"{s}\"<r> not found <d>{s}{s} 404<r>\n";
- const args = .{
- name.slice(),
- task.http.url.displayHostname(),
- task.http.url.pathname,
- };
+ if (@TypeOf(callbacks.onPackageManifestError) != void) {
+ const err: PackageManifestError = switch (response.status_code) {
+ 400 => error.PackageManifestHTTP400,
+ 401 => error.PackageManifestHTTP401,
+ 402 => error.PackageManifestHTTP402,
+ 403 => error.PackageManifestHTTP403,
+ 404 => error.PackageManifestHTTP404,
+ 405...499 => error.PackageManifestHTTP4xx,
+ else => error.PackageManifestHTTP5xx,
+ };
- if (comptime log_level.showProgress()) {
- Output.prettyWithPrinterFn(fmt, args, Progress.log, &manager.progress);
- } else {
- Output.prettyErrorln(fmt, args);
- Output.flush();
+ if (manager.dynamic_root_dependencies) |*root_deps| {
+ var deps: []Dependency.Pair = root_deps.items;
+ for (deps) |*dep| {
+ if (strings.eql(manager.lockfile.str(dep.dependency.name), name.slice())) {
+ dep.failed = dep.failed orelse err;
}
}
- },
- 401 => {
- if (comptime log_level != .silent) {
- const fmt = "\n<r><red>error<r>: unauthorized <b>\"{s}\"<r> <d>{s}{s} 401<r>\n";
- const args = .{
- name.slice(),
- task.http.url.displayHostname(),
- task.http.url.pathname,
- };
+ }
- if (comptime log_level.showProgress()) {
- Output.prettyWithPrinterFn(fmt, args, Progress.log, &manager.progress);
- } else {
- Output.prettyErrorln(fmt, args);
- Output.flush();
+ callbacks.onPackageManifestError(
+ extract_ctx,
+ name.slice(),
+ err,
+ task.url_buf,
+ );
+ } else {
+ switch (response.status_code) {
+ 404 => {
+ if (comptime log_level != .silent) {
+ const fmt = "\n<r><red>error<r>: package <b>\"{s}\"<r> not found <d>{s}{s} 404<r>\n";
+ const args = .{
+ name.slice(),
+ task.http.url.displayHostname(),
+ task.http.url.pathname,
+ };
+
+ if (comptime log_level.showProgress()) {
+ Output.prettyWithPrinterFn(fmt, args, Progress.log, &manager.progress);
+ } else {
+ Output.prettyErrorln(fmt, args);
+ Output.flush();
+ }
}
- }
- },
- 403 => {
- if (comptime log_level != .silent) {
- const fmt = "\n<r><red>error<r>: forbidden while loading <b>\"{s}\"<r><d> 403<r>\n";
- const args = .{
- name.slice(),
- };
-
- if (comptime log_level.showProgress()) {
- Output.prettyWithPrinterFn(fmt, args, Progress.log, &manager.progress);
- } else {
- Output.prettyErrorln(fmt, args);
- Output.flush();
+ },
+ 401 => {
+ if (comptime log_level != .silent) {
+ const fmt = "\n<r><red>error<r>: unauthorized <b>\"{s}\"<r> <d>{s}{s} 401<r>\n";
+ const args = .{
+ name.slice(),
+ task.http.url.displayHostname(),
+ task.http.url.pathname,
+ };
+
+ if (comptime log_level.showProgress()) {
+ Output.prettyWithPrinterFn(fmt, args, Progress.log, &manager.progress);
+ } else {
+ Output.prettyErrorln(fmt, args);
+ Output.flush();
+ }
}
- }
- },
- else => {
- if (comptime log_level != .silent) {
- const fmt = "\n<r><red><b>GET<r><red> {s}<d> - {d}<r>\n";
- const args = .{
- task.http.client.url.href,
- response.status_code,
- };
-
- if (comptime log_level.showProgress()) {
- Output.prettyWithPrinterFn(fmt, args, Progress.log, &manager.progress);
- } else {
- Output.prettyErrorln(fmt, args);
- Output.flush();
+ },
+ 403 => {
+ if (comptime log_level != .silent) {
+ const fmt = "\n<r><red>error<r>: forbidden while loading <b>\"{s}\"<r><d> 403<r>\n";
+ const args = .{
+ name.slice(),
+ };
+
+ if (comptime log_level.showProgress()) {
+ Output.prettyWithPrinterFn(fmt, args, Progress.log, &manager.progress);
+ } else {
+ Output.prettyErrorln(fmt, args);
+ Output.flush();
+ }
}
- }
- },
+ },
+ else => {
+ if (comptime log_level != .silent) {
+ const fmt = "\n<r><red><b>GET<r><red> {s}<d> - {d}<r>\n";
+ const args = .{
+ task.http.client.url.href,
+ response.status_code,
+ };
+
+ if (comptime log_level.showProgress()) {
+ Output.prettyWithPrinterFn(fmt, args, Progress.log, &manager.progress);
+ } else {
+ Output.prettyErrorln(fmt, args);
+ Output.flush();
+ }
+ }
+ },
+ }
}
for (manager.package_json_updates) |*update| {
if (strings.eql(update.name, name.slice())) {
@@ -2635,9 +3136,14 @@ pub const PackageManager = struct {
if (manifest_req.loaded_manifest) |manifest| {
var entry = try manager.manifests.getOrPut(manager.allocator, manifest.pkg.name.hash);
entry.value_ptr.* = manifest;
- entry.value_ptr.*.pkg.public_max_age = @truncate(u32, @intCast(u64, @maximum(0, std.time.timestamp()))) + 300;
+
+ if (timestamp_this_tick == null) {
+ timestamp_this_tick = @truncate(u32, @intCast(u64, @maximum(0, std.time.timestamp()))) +| 300;
+ }
+
+ entry.value_ptr.*.pkg.public_max_age = timestamp_this_tick.?;
{
- Npm.PackageManifest.Serializer.save(entry.value_ptr, PackageManager.instance.getTemporaryDirectory(), PackageManager.instance.getCacheDirectory()) catch {};
+ Npm.PackageManifest.Serializer.save(entry.value_ptr, manager.getTemporaryDirectory(), manager.getCacheDirectory()) catch {};
}
var dependency_list_entry = manager.task_queue.getEntry(task.task_id).?;
@@ -2645,20 +3151,7 @@ pub const PackageManager = struct {
var dependency_list = dependency_list_entry.value_ptr.*;
dependency_list_entry.value_ptr.* = .{};
- if (dependency_list.items.len > 0) {
- for (dependency_list.items) |item| {
- var dependency = manager.lockfile.buffers.dependencies.items[item.dependency];
- var resolution = manager.lockfile.buffers.resolutions.items[item.dependency];
-
- try manager.enqueueDependency(
- item.dependency,
- dependency,
- resolution,
- );
- }
-
- dependency_list.deinit(manager.allocator);
- }
+ try manager.processDependencyList(dependency_list, ExtractCompletionContext, extract_ctx, callbacks);
manager.flushDependencyQueue();
continue;
@@ -2669,23 +3162,71 @@ pub const PackageManager = struct {
},
.extract => |extract| {
const response = task.http.response orelse {
- const fmt = "\n<r><red>error<r>: {s} downloading tarball <b>{s}@{s}<r>\n";
- const error_name: string = if (task.http.err) |err| std.mem.span(@errorName(err)) else "failed";
- const args = .{ error_name, extract.name.slice(), extract.resolution.fmt(manager.lockfile.buffers.string_bytes.items) };
+ const err = task.http.err orelse error.TarballFailedToDownload;
- if (comptime log_level != .silent) {
- if (comptime log_level.showProgress()) {
- Output.prettyWithPrinterFn(fmt, args, Progress.log, &manager.progress);
- } else {
- Output.prettyErrorln(fmt, args);
- Output.flush();
+ if (@TypeOf(callbacks.onPackageDownloadError) != void) {
+ if (manager.dynamic_root_dependencies) |*root_deps| {
+ for (root_deps.items) |*dep| {
+ if (dep.resolution_id == extract.package_id) {
+ dep.failed = err;
+ }
+ }
+ }
+ callbacks.onPackageDownloadError(
+ extract_ctx,
+ extract.package_id,
+ extract.name.slice(),
+ extract.resolution,
+ err,
+ task.url_buf,
+ );
+ } else {
+ const fmt = "\n<r><red>error<r>: {s} downloading tarball <b>{s}@{s}<r>\n";
+ const error_name: string = std.mem.span(@errorName(err));
+ const args = .{ error_name, extract.name.slice(), extract.resolution.fmt(manager.lockfile.buffers.string_bytes.items) };
+
+ if (comptime log_level != .silent) {
+ if (comptime log_level.showProgress()) {
+ Output.prettyWithPrinterFn(fmt, args, Progress.log, &manager.progress);
+ } else {
+ Output.prettyErrorln(fmt, args);
+ Output.flush();
+ }
}
}
+
continue;
};
if (response.status_code > 399) {
- if (comptime log_level != .silent) {
+ if (@TypeOf(callbacks.onPackageDownloadError) != void) {
+ const err = switch (response.status_code) {
+ 400 => error.TarballHTTP400,
+ 401 => error.TarballHTTP401,
+ 402 => error.TarballHTTP402,
+ 403 => error.TarballHTTP403,
+ 404 => error.TarballHTTP404,
+ 405...499 => error.TarballHTTP4xx,
+ else => error.TarballHTTP5xx,
+ };
+
+ if (manager.dynamic_root_dependencies) |*root_deps| {
+ for (root_deps.items) |*dep| {
+ if (dep.resolution_id == extract.package_id) {
+ dep.failed = err;
+ }
+ }
+ }
+
+ callbacks.onPackageDownloadError(
+ extract_ctx,
+ extract.package_id,
+ extract.name.slice(),
+ extract.resolution,
+ err,
+ task.url_buf,
+ );
+ } else if (comptime log_level != .silent) {
const fmt = "\n<r><red><b>GET<r><red> {s}<d> - {d}<r>\n";
const args = .{
task.http.client.url.href,
@@ -2702,6 +3243,7 @@ pub const PackageManager = struct {
Output.flush();
}
}
+
continue;
}
@@ -2740,11 +3282,30 @@ pub const PackageManager = struct {
switch (task.tag) {
.package_manifest => {
if (task.status == .fail) {
- if (comptime log_level != .silent) {
+ const name = task.request.package_manifest.name;
+ const err = task.err orelse error.Failed;
+
+ if (@TypeOf(callbacks.onPackageManifestError) != void) {
+ if (manager.dynamic_root_dependencies) |*root_deps| {
+ var deps: []Dependency.Pair = root_deps.items;
+ for (deps) |*dep| {
+ if (strings.eql(manager.lockfile.str(dep.dependency.name), name.slice())) {
+ dep.failed = dep.failed orelse err;
+ }
+ }
+ }
+
+ callbacks.onPackageManifestError(
+ extract_ctx,
+ name.slice(),
+ err,
+ task.request.package_manifest.network.url_buf,
+ );
+ } else if (comptime log_level != .silent) {
const fmt = "\n<r><red>rerror<r>: {s} parsing package manifest for <b>{s}<r>";
- const error_name: string = if (task.err != null) std.mem.span(@errorName(task.err.?)) else @as(string, "Failed");
+ const error_name: string = @errorName(err);
- const args = .{ error_name, task.request.package_manifest.name.slice() };
+ const args = .{ error_name, name.slice() };
if (comptime log_level.showProgress()) {
Output.prettyWithPrinterFn(fmt, args, Progress.log, &manager.progress);
} else {
@@ -2764,20 +3325,7 @@ pub const PackageManager = struct {
var dependency_list = dependency_list_entry.value_ptr.*;
dependency_list_entry.value_ptr.* = .{};
- if (dependency_list.items.len > 0) {
- for (dependency_list.items) |item| {
- var dependency = manager.lockfile.buffers.dependencies.items[item.dependency];
- var resolution = manager.lockfile.buffers.resolutions.items[item.dependency];
-
- try manager.enqueueDependency(
- item.dependency,
- dependency,
- resolution,
- );
- }
-
- dependency_list.deinit(manager.allocator);
- }
+ try manager.processDependencyList(dependency_list, ExtractCompletionContext, extract_ctx, callbacks);
if (comptime log_level.showProgress()) {
if (!has_updated_this_run) {
@@ -2788,9 +3336,28 @@ pub const PackageManager = struct {
},
.extract => {
if (task.status == .fail) {
- if (comptime log_level != .silent) {
+ const err = task.err orelse error.TarballFailedToExtract;
+ if (@TypeOf(callbacks.onPackageDownloadError) != void) {
+ if (manager.dynamic_root_dependencies) |*root_deps| {
+ var deps: []Dependency.Pair = root_deps.items;
+ for (deps) |*dep| {
+ if (dep.resolution_id == task.request.extract.tarball.package_id) {
+ dep.failed = dep.failed orelse err;
+ }
+ }
+ }
+
+ callbacks.onPackageDownloadError(
+ extract_ctx,
+ task.request.extract.tarball.package_id,
+ task.request.extract.tarball.name.slice(),
+ task.request.extract.tarball.resolution,
+ err,
+ task.request.extract.network.url_buf,
+ );
+ } else if (comptime log_level != .silent) {
const fmt = "<r><red>error<r>: {s} extracting tarball for <b>{s}<r>";
- const error_name: string = if (task.err != null) std.mem.span(@errorName(task.err.?)) else @as(string, "Failed");
+ const error_name: string = @errorName(err);
const args = .{
error_name,
task.request.extract.tarball.name.slice(),
@@ -2809,10 +3376,11 @@ pub const PackageManager = struct {
}
const package_id = task.request.extract.tarball.package_id;
manager.extracted_count += 1;
+ bun.Analytics.Features.extracted_packages = true;
manager.setPreinstallState(package_id, manager.lockfile, .done);
- if (comptime ExtractCompletionContext != void) {
- callback_fn(extract_ctx, package_id, comptime log_level);
+ if (comptime @TypeOf(callbacks.onExtract) != void) {
+ callbacks.onExtract(extract_ctx, package_id, comptime log_level);
}
if (comptime log_level.showProgress()) {
@@ -2839,7 +3407,7 @@ pub const PackageManager = struct {
manager.network_resolve_batch = .{};
if (comptime log_level.showProgress()) {
- if (comptime ExtractCompletionContext == void) {
+ if (comptime ExtractCompletionContext == void or (@hasField(@TypeOf(callbacks), "progress_bar") and callbacks.progress_bar == true)) {
const completed_items = manager.total_tasks - manager.pending_tasks;
if (completed_items != manager.downloads_node.?.unprotected_completed_items or has_updated_this_run) {
manager.downloads_node.?.setCompletedItems(completed_items);
@@ -3598,7 +4166,7 @@ pub const PackageManager = struct {
return try initWithCLI(_ctx, package_json_file_, cli);
}
- fn initWithCLI(
+ pub fn initWithCLI(
ctx: Command.Context,
package_json_file_: ?std.fs.File,
cli: CommandLineArguments,
@@ -3748,7 +4316,132 @@ pub const PackageManager = struct {
ctx.install,
);
- manager.timestamp = @truncate(u32, @intCast(u64, @maximum(std.time.timestamp(), 0)));
+ manager.timestamp_for_manifest_cache_control = @truncate(u32, @intCast(u64, @maximum(std.time.timestamp(), 0)));
+ return manager;
+ }
+
+ pub fn initWithRuntime(
+ log: *logger.Log,
+ bun_install: ?*Api.BunInstall,
+ allocator: std.mem.Allocator,
+ cli: CommandLineArguments,
+ env_loader: *DotEnv.Loader,
+ ) !*PackageManager {
+ if (env_loader.map.get("BUN_INSTALL_VERBOSE") != null) {
+ PackageManager.verbose_install = true;
+ }
+
+ var cpu_count = @truncate(u32, ((try std.Thread.getCpuCount()) + 1));
+
+ if (env_loader.map.get("GOMAXPROCS")) |max_procs| {
+ if (std.fmt.parseInt(u32, max_procs, 10)) |cpu_count_| {
+ cpu_count = @minimum(cpu_count, cpu_count_);
+ } else |_| {}
+ }
+
+ var manager = &instance;
+ var root_dir = try Fs.FileSystem.instance.fs.readDirectory(
+ Fs.FileSystem.instance.top_level_dir,
+ null,
+ );
+ // var progress = Progress{};
+ // var node = progress.start(name: []const u8, estimated_total_items: usize)
+ manager.* = PackageManager{
+ .options = .{},
+ .network_task_fifo = NetworkQueue.init(),
+ .env_loader = env_loader,
+ .allocator = allocator,
+ .log = log,
+ .root_dir = &root_dir.entries,
+ .env = env_loader,
+ .cpu_count = cpu_count,
+ .thread_pool = ThreadPool.init(.{
+ .max_threads = cpu_count,
+ }),
+ .resolve_tasks = TaskChannel.init(),
+ .lockfile = undefined,
+ .root_package_json_file = undefined,
+ .waiter = try Waker.init(allocator),
+ };
+ manager.lockfile = try allocator.create(Lockfile);
+
+ if (Output.enable_ansi_colors_stderr) {
+ manager.progress = Progress{};
+ manager.progress.supports_ansi_escape_codes = Output.enable_ansi_colors_stderr;
+ manager.root_progress_node = manager.progress.start("", 0);
+ manager.root_download_node = manager.root_progress_node.start(ProgressStrings.download(), 0);
+ }
+
+ if (!manager.options.enable.cache) {
+ manager.options.enable.manifest_cache = false;
+ manager.options.enable.manifest_cache_control = false;
+ }
+
+ if (env_loader.map.get("BUN_MANIFEST_CACHE")) |manifest_cache| {
+ if (strings.eqlComptime(manifest_cache, "1")) {
+ manager.options.enable.manifest_cache = true;
+ manager.options.enable.manifest_cache_control = false;
+ } else if (strings.eqlComptime(manifest_cache, "2")) {
+ manager.options.enable.manifest_cache = true;
+ manager.options.enable.manifest_cache_control = true;
+ } else {
+ manager.options.enable.manifest_cache = false;
+ manager.options.enable.manifest_cache_control = false;
+ }
+ }
+
+ try manager.options.load(
+ allocator,
+ log,
+ env_loader,
+ cli,
+ bun_install,
+ );
+
+ manager.timestamp_for_manifest_cache_control = @truncate(
+ u32,
+ @intCast(
+ u64,
+ @maximum(
+ std.time.timestamp(),
+ 0,
+ ),
+ ),
+ // When using "bun install", we check for updates with a 300 second cache.
+ // When using bun, we only do staleness checks once per day
+ ) -| std.time.s_per_day;
+
+ manager.lockfile = brk: {
+ var buf: [bun.MAX_PATH_BYTES]u8 = undefined;
+
+ if (root_dir.entries.hasComptimeQuery("bun.lockb")) {
+ var parts = [_]string{
+ "./bun.lockb",
+ };
+ var lockfile_path = Path.joinAbsStringBuf(
+ Fs.FileSystem.instance.top_level_dir,
+ &buf,
+ &parts,
+ .auto,
+ );
+ buf[lockfile_path.len] = 0;
+ var lockfile_path_z = std.meta.assumeSentinel(buf[0..lockfile_path.len], 0);
+
+ const result = manager.lockfile.loadFromDisk(
+ allocator,
+ log,
+ lockfile_path_z,
+ );
+
+ if (result == .ok) {
+ break :brk result.ok;
+ }
+ }
+
+ try manager.lockfile.initEmpty(allocator);
+ break :brk manager.lockfile;
+ };
+
return manager;
}
@@ -4516,7 +5209,24 @@ pub const PackageManager = struct {
}
var updates = UpdateRequest.parse(ctx.allocator, ctx.log, manager.options.positionals[1..], &update_requests, op);
+ try updatePackageJSONAndInstallWithManagerWithUpdates(
+ ctx,
+ manager,
+ updates,
+ false,
+ op,
+ log_level,
+ );
+ }
+ pub fn updatePackageJSONAndInstallWithManagerWithUpdates(
+ ctx: Command.Context,
+ manager: *PackageManager,
+ updates: []UpdateRequest,
+ auto_free: bool,
+ comptime op: Lockfile.Package.Diff.Op,
+ comptime log_level: Options.LogLevel,
+ ) !void {
if (ctx.log.errors > 0) {
if (comptime log_level != .silent) {
if (Output.enable_ansi_colors) {
@@ -4672,7 +5382,9 @@ pub const PackageManager = struct {
var new_package_json_source = try ctx.allocator.dupe(u8, package_json_writer.ctx.writtenWithoutTrailingZero());
// Do not free the old package.json AST nodes
- _ = JSAst.Expr.Data.Store.toOwnedSlice();
+ var old_ast_nodes = JSAst.Expr.Data.Store.toOwnedSlice();
+ // haha unless
+ defer if (auto_free) bun.default_allocator.free(old_ast_nodes);
try installWithManager(ctx, manager, new_package_json_source, log_level);
@@ -5054,28 +5766,15 @@ pub const PackageManager = struct {
switch (resolution.tag) {
.npm => {
std.debug.assert(resolution.value.npm.url.len() > 0);
-
- const task_id = Task.Id.forNPMPackage(Task.Tag.extract, name, resolution.value.npm.version);
- var task_queue = this.manager.task_queue.getOrPut(this.manager.allocator, task_id) catch unreachable;
- if (!task_queue.found_existing) {
- task_queue.value_ptr.* = .{};
- }
-
- task_queue.value_ptr.append(
- this.manager.allocator,
+ this.manager.enqueuePackageForDownload(
+ name,
+ package_id,
+ resolution.value.npm.version,
+ resolution.value.npm.url.slice(buf),
.{
.node_modules_folder = @intCast(u32, this.node_modules_folder.fd),
},
- ) catch unreachable;
-
- if (!task_queue.found_existing) {
- if (this.manager.generateNetworkTaskForTarball(task_id, resolution.value.npm.url.slice(buf), this.lockfile.packages.get(package_id)) catch unreachable) |task| {
- task.schedule(&this.manager.network_tarball_batch);
- if (this.manager.network_tarball_batch.len > 0) {
- _ = this.manager.scheduleNetworkTasks();
- }
- }
- }
+ );
},
else => {
Output.prettyErrorln(
@@ -5128,6 +5827,35 @@ pub const PackageManager = struct {
}
};
+ pub fn enqueuePackageForDownload(
+ this: *PackageManager,
+ name: []const u8,
+ package_id: PackageID,
+ version: Semver.Version,
+ url: []const u8,
+ task_context: TaskCallbackContext,
+ ) void {
+ const task_id = Task.Id.forNPMPackage(Task.Tag.extract, name, version);
+ var task_queue = this.task_queue.getOrPut(this.allocator, task_id) catch unreachable;
+ if (!task_queue.found_existing) {
+ task_queue.value_ptr.* = .{};
+ }
+
+ task_queue.value_ptr.append(
+ this.allocator,
+ task_context,
+ ) catch unreachable;
+
+ if (!task_queue.found_existing) {
+ if (this.generateNetworkTaskForTarball(task_id, url, this.lockfile.packages.get(package_id)) catch unreachable) |task| {
+ task.schedule(&this.network_tarball_batch);
+ if (this.network_tarball_batch.len > 0) {
+ _ = this.scheduleNetworkTasks();
+ }
+ }
+ }
+ }
+
pub fn installPackages(
this: *PackageManager,
lockfile_: *Lockfile,
@@ -5258,7 +5986,12 @@ pub const PackageManager = struct {
try this.runTasks(
*PackageInstaller,
&installer,
- PackageInstaller.installEnqueuedPackages,
+ .{
+ .onExtract = PackageInstaller.installEnqueuedPackages,
+ .onResolve = void{},
+ .onPackageManifestError = void{},
+ .onPackageDownloadError = void{},
+ },
log_level,
);
if (!installer.options.do.install_packages) return error.InstallFailed;
@@ -5272,7 +6005,12 @@ pub const PackageManager = struct {
try this.runTasks(
*PackageInstaller,
&installer,
- PackageInstaller.installEnqueuedPackages,
+ .{
+ .onExtract = PackageInstaller.installEnqueuedPackages,
+ .onResolve = void{},
+ .onPackageManifestError = void{},
+ .onPackageDownloadError = void{},
+ },
log_level,
);
if (!installer.options.do.install_packages) return error.InstallFailed;
@@ -5282,7 +6020,12 @@ pub const PackageManager = struct {
try this.runTasks(
*PackageInstaller,
&installer,
- PackageInstaller.installEnqueuedPackages,
+ .{
+ .onExtract = PackageInstaller.installEnqueuedPackages,
+ .onResolve = void{},
+ .onPackageManifestError = void{},
+ .onPackageDownloadError = void{},
+ },
log_level,
);
}
@@ -5386,6 +6129,31 @@ pub const PackageManager = struct {
manager.options.bin_path = std.meta.assumeSentinel(try FileSystem.instance.dirname_store.append([:0]u8, result_), 0);
}
+ pub fn startProgressBarIfNone(manager: *PackageManager) void {
+ if (manager.downloads_node == null) {
+ manager.startProgressBar();
+ }
+ }
+ pub fn startProgressBar(manager: *PackageManager) void {
+ manager.downloads_node = manager.progress.start(ProgressStrings.download(), 0);
+ manager.progress.supports_ansi_escape_codes = Output.enable_ansi_colors_stderr;
+ manager.setNodeName(manager.downloads_node.?, ProgressStrings.download_no_emoji_, ProgressStrings.download_emoji, true);
+ manager.downloads_node.?.setEstimatedTotalItems(manager.total_tasks + manager.extracted_count);
+ manager.downloads_node.?.setCompletedItems(manager.total_tasks - manager.pending_tasks);
+ manager.downloads_node.?.activate();
+ manager.progress.refresh();
+ }
+
+ pub fn endProgressBar(manager: *PackageManager) void {
+ var downloads_node = manager.downloads_node orelse return;
+ downloads_node.setEstimatedTotalItems(downloads_node.unprotected_estimated_total_items);
+ downloads_node.setCompletedItems(downloads_node.unprotected_estimated_total_items);
+ manager.progress.refresh();
+ manager.progress.root.end();
+ manager.progress = .{};
+ manager.downloads_node = null;
+ }
+
fn installWithManager(
ctx: Command.Context,
manager: *PackageManager,
@@ -5637,13 +6405,7 @@ pub const PackageManager = struct {
}
if (comptime log_level.showProgress()) {
- manager.downloads_node = manager.progress.start(ProgressStrings.download(), 0);
- manager.progress.supports_ansi_escape_codes = Output.enable_ansi_colors_stderr;
- manager.setNodeName(manager.downloads_node.?, ProgressStrings.download_no_emoji_, ProgressStrings.download_emoji, true);
- manager.downloads_node.?.setEstimatedTotalItems(manager.total_tasks + manager.extracted_count);
- manager.downloads_node.?.setCompletedItems(manager.total_tasks - manager.pending_tasks);
- manager.downloads_node.?.activate();
- manager.progress.refresh();
+ manager.startProgressBar();
} else if (comptime log_level != .silent) {
Output.prettyErrorln(" Resolving dependencies", .{});
Output.flush();
@@ -5651,17 +6413,17 @@ pub const PackageManager = struct {
{
while (manager.pending_tasks > 0) : (manager.sleep()) {
- try manager.runTasks(void, void{}, null, log_level);
+ try manager.runTasks(void, void{}, .{
+ .onExtract = void{},
+ .onResolve = void{},
+ .onPackageManifestError = void{},
+ .onPackageDownloadError = void{},
+ }, log_level);
}
}
if (comptime log_level.showProgress()) {
- manager.downloads_node.?.setEstimatedTotalItems(manager.downloads_node.?.unprotected_estimated_total_items);
- manager.downloads_node.?.setCompletedItems(manager.downloads_node.?.unprotected_estimated_total_items);
- manager.progress.refresh();
- manager.progress.root.end();
- manager.progress = .{};
- manager.downloads_node = null;
+ manager.endProgressBar();
} else if (comptime log_level != .silent) {
Output.prettyErrorln(" Resolved, downloaded and extracted [{d}]", .{manager.total_tasks});
Output.flush();
@@ -5986,3 +6748,13 @@ test "UpdateRequests.parse" {
try std.testing.expectEqualStrings(reqs[5].version.literal.slice("bing@1.0.0"), "latest");
try std.testing.expectEqual(updates.len, 6);
}
+
+pub const PackageManifestError = error{
+ PackageManifestHTTP400,
+ PackageManifestHTTP401,
+ PackageManifestHTTP402,
+ PackageManifestHTTP403,
+ PackageManifestHTTP404,
+ PackageManifestHTTP4xx,
+ PackageManifestHTTP5xx,
+};
diff --git a/src/install/lockfile.zig b/src/install/lockfile.zig
index cca34b20b..dfc49ddd0 100644
--- a/src/install/lockfile.zig
+++ b/src/install/lockfile.zig
@@ -14,6 +14,7 @@ const JSLexer = @import("../js_lexer.zig");
const logger = @import("../logger.zig");
const js_parser = @import("../js_parser.zig");
+const Expr = @import("../js_ast.zig").Expr;
const json_parser = @import("../json_parser.zig");
const JSPrinter = @import("../js_printer.zig");
@@ -82,6 +83,8 @@ const Crypto = @import("../sha.zig").Hashers;
pub const MetaHash = [std.crypto.hash.sha2.Sha512256.digest_length]u8;
const zero_hash = std.mem.zeroes(MetaHash);
+const PackageJSON = @import("../resolver/package_json.zig").PackageJSON;
+
pub const ExternalStringBuilder = StructBuilder.Builder(ExternalString);
pub const SmallExternalStringList = ExternalSlice(String);
@@ -1661,7 +1664,8 @@ pub const StringBuilder = struct {
}
}
- pub fn allocatedSlice(this: *StringBuilder) ![]u8 {
+ pub fn allocatedSlice(this: *StringBuilder) []const u8 {
+ if (this.ptr == null) return "";
return this.ptr.?[0..this.cap];
}
@@ -1796,7 +1800,7 @@ pub const StringBuffer = std.ArrayListUnmanaged(u8);
pub const ExternalStringBuffer = std.ArrayListUnmanaged(ExternalString);
pub const Package = extern struct {
- const DependencyGroup = struct {
+ pub const DependencyGroup = struct {
prop: string,
field: string,
behavior: Behavior,
@@ -1933,6 +1937,125 @@ pub const Package = extern struct {
return new_package.meta.id;
}
+ pub fn fromPackageJSON(
+ allocator: std.mem.Allocator,
+ lockfile: *Lockfile,
+ log: *logger.Log,
+ package_json: *PackageJSON,
+ comptime features: Features,
+ ) !Lockfile.Package {
+ var package = Lockfile.Package{};
+
+ // var string_buf = package_json;
+
+ var string_builder = lockfile.stringBuilder();
+
+ var total_dependencies_count: u32 = 0;
+ // var bin_extern_strings_count: u32 = 0;
+
+ // --- Counting
+ {
+ string_builder.count(package_json.name);
+ string_builder.count(package_json.version);
+ var dependencies = package_json.dependencies.map.values();
+ for (dependencies) |dep| {
+ if (dep.behavior.isEnabled(features)) {
+ dep.count(package_json.dependencies.source_buf, @TypeOf(&string_builder), &string_builder);
+ total_dependencies_count += 1;
+ }
+ }
+ }
+
+ // string_builder.count(manifest.str(package_version_ptr.tarball_url));
+
+ try string_builder.allocate();
+ defer string_builder.clamp();
+ // var extern_strings_list = &lockfile.buffers.extern_strings;
+ var dependencies_list = &lockfile.buffers.dependencies;
+ var resolutions_list = &lockfile.buffers.resolutions;
+ try dependencies_list.ensureUnusedCapacity(lockfile.allocator, total_dependencies_count);
+ try resolutions_list.ensureUnusedCapacity(lockfile.allocator, total_dependencies_count);
+ // try extern_strings_list.ensureUnusedCapacity(lockfile.allocator, bin_extern_strings_count);
+ // extern_strings_list.items.len += bin_extern_strings_count;
+
+ // -- Cloning
+ {
+ const package_name: ExternalString = string_builder.append(ExternalString, package_json.name);
+ package.name_hash = package_name.hash;
+ package.name = package_name.value;
+ var package_version = string_builder.append(String, package_json.version);
+ var buf = string_builder.allocatedSlice();
+
+ const version: Dependency.Version = brk: {
+ if (package_json.version.len > 0) {
+ const sliced = package_version.sliced(buf);
+ const name = package.name.slice(buf);
+ if (Dependency.parse(allocator, name, &sliced, log)) |dep| {
+ break :brk dep;
+ }
+ }
+
+ break :brk Dependency.Version{};
+ };
+
+ if (version.tag == .npm and version.value.npm.isExact()) {
+ package.resolution = Resolution{
+ .value = .{
+ .npm = .{
+ .version = version.value.npm.toVersion(),
+ .url = .{},
+ },
+ },
+ .tag = .npm,
+ };
+ } else {
+ package.resolution = Resolution{
+ .value = .{
+ .root = {},
+ },
+ .tag = .root,
+ };
+ }
+ const total_len = dependencies_list.items.len + total_dependencies_count;
+ std.debug.assert(dependencies_list.items.len == resolutions_list.items.len);
+
+ var dependencies: []Dependency = dependencies_list.items.ptr[dependencies_list.items.len..total_len];
+ std.mem.set(Dependency, dependencies, Dependency{});
+
+ const package_dependencies = package_json.dependencies.map.values();
+ const source_buf = package_json.dependencies.source_buf;
+ for (package_dependencies) |dep| {
+ if (!dep.behavior.isEnabled(features)) continue;
+
+ dependencies[0] = try dep.clone(source_buf, @TypeOf(&string_builder), &string_builder);
+ dependencies = dependencies[1..];
+ if (dependencies.len == 0) break;
+ }
+
+ // We lose the bin info here
+ // package.bin = package_version.bin.clone(string_buf, manifest.extern_strings_bin_entries, extern_strings_list.items, extern_strings_slice, @TypeOf(&string_builder), &string_builder);
+ // and the integriy hash
+ // package.meta.integrity = package_version.integrity;
+
+ package.meta.arch = package_json.arch;
+ package.meta.os = package_json.os;
+
+ package.dependencies.off = @truncate(u32, dependencies_list.items.len);
+ package.dependencies.len = total_dependencies_count - @truncate(u32, dependencies.len);
+ package.resolutions.off = package.dependencies.off;
+ package.resolutions.len = package.dependencies.len;
+
+ const new_length = package.dependencies.len + dependencies_list.items.len;
+
+ std.mem.set(PackageID, resolutions_list.items.ptr[package.dependencies.off .. package.dependencies.off + package.dependencies.len], invalid_package_id);
+
+ dependencies_list.items = dependencies_list.items.ptr[0..new_length];
+ resolutions_list.items = resolutions_list.items.ptr[0..new_length];
+
+ return package;
+ }
+ }
+
pub fn fromNPM(
allocator: std.mem.Allocator,
lockfile: *Lockfile,
@@ -2250,6 +2373,30 @@ pub const Package = extern struct {
Global.exit(1);
};
+ try parseWithJSON(
+ package,
+ lockfile,
+ allocator,
+ log,
+ source,
+ json,
+ ResolverContext,
+ resolver,
+ features,
+ );
+ }
+
+ pub fn parseWithJSON(
+ package: *Lockfile.Package,
+ lockfile: *Lockfile,
+ allocator: std.mem.Allocator,
+ log: *logger.Log,
+ source: logger.Source,
+ json: Expr,
+ comptime ResolverContext: type,
+ resolver: ResolverContext,
+ comptime features: Features,
+ ) !void {
var string_builder = lockfile.stringBuilder();
var total_dependencies_count: u32 = 0;
@@ -3092,3 +3239,37 @@ pub fn generateMetaHash(this: *Lockfile, print_name_version_string: bool) !MetaH
return digest;
}
+
+pub fn resolve(this: *Lockfile, package_name: []const u8, version: Dependency.Version) ?PackageID {
+ const name_hash = bun.hash(package_name);
+ const entry = this.package_index.get(name_hash) orelse return null;
+ const can_satisfy = version.tag == .npm;
+
+ switch (entry) {
+ .PackageID => |id| {
+ const resolutions = this.packages.items(.resolution);
+
+ if (can_satisfy and version.value.npm.satisfies(resolutions[id].value.npm.version)) {
+ return id;
+ }
+ },
+ .PackageIDMultiple => |multi_| {
+ const multi = std.mem.span(multi_);
+ const resolutions = this.packages.items(.resolution);
+
+ for (multi) |id| {
+ if (comptime Environment.isDebug or Environment.isTest) {
+ std.debug.assert(id != invalid_package_id);
+ }
+
+ if (id == invalid_package_id - 1) return null;
+
+ if (can_satisfy and version.value.npm.satisfies(resolutions[id].value.npm.version)) {
+ return id;
+ }
+ }
+ },
+ }
+
+ return null;
+}
diff --git a/src/install/npm.zig b/src/install/npm.zig
index 1ff5b0c2f..2be84624c 100644
--- a/src/install/npm.zig
+++ b/src/install/npm.zig
@@ -163,6 +163,7 @@ pub const Registry = struct {
log: *logger.Log,
package_name: string,
loaded_manifest: ?PackageManifest,
+ package_manager: *PackageManager,
) !PackageVersionResponse {
switch (response.status_code) {
400 => return error.BadRequest,
@@ -193,7 +194,6 @@ pub const Registry = struct {
}
}
- initializeStore();
var new_etag_buf: [64]u8 = undefined;
if (new_etag.len < new_etag_buf.len) {
@@ -210,8 +210,8 @@ pub const Registry = struct {
new_etag,
@truncate(u32, @intCast(u64, @maximum(0, std.time.timestamp()))) + 300,
)) |package| {
- if (PackageManager.instance.options.enable.manifest_cache) {
- PackageManifest.Serializer.save(&package, PackageManager.instance.getTemporaryDirectory(), PackageManager.instance.getCacheDirectory()) catch {};
+ if (package_manager.options.enable.manifest_cache) {
+ PackageManifest.Serializer.save(&package, package_manager.getTemporaryDirectory(), package_manager.getCacheDirectory()) catch {};
}
return PackageVersionResponse{ .fresh = package };
diff --git a/src/install/resolvers/folder_resolver.zig b/src/install/resolvers/folder_resolver.zig
index bf9d5a78b..b48600747 100644
--- a/src/install/resolvers/folder_resolver.zig
+++ b/src/install/resolvers/folder_resolver.zig
@@ -12,6 +12,7 @@ const IdentityContext = @import("../../identity_context.zig").IdentityContext;
const strings = @import("strings");
const Resolution = @import("../resolution.zig").Resolution;
const String = @import("../semver.zig").String;
+const Semver = @import("../semver.zig");
const bun = @import("../../global.zig");
const Dependency = @import("../dependency.zig");
pub const FolderResolution = union(Tag) {
@@ -50,6 +51,24 @@ pub const FolderResolution = union(Tag) {
pub const Resolver = NewResolver(Resolution.Tag.folder);
pub const SymlinkResolver = NewResolver(Resolution.Tag.symlink);
+ pub const CacheFolderResolver = struct {
+ folder_path: []const u8 = "",
+ version: Semver.Version,
+
+ pub fn resolve(this: @This(), comptime Builder: type, _: Builder, _: JSAst.Expr) !Resolution {
+ return Resolution{
+ .tag = Resolution.Tag.npm,
+ .value = .{
+ .npm = .{
+ .version = this.version,
+ .url = String.init("", ""),
+ },
+ },
+ };
+ }
+
+ pub fn count(_: @This(), comptime Builder: type, _: Builder, _: JSAst.Expr) void {}
+ };
pub fn normalizePackageJSONPath(global_or_relative: GlobalOrRelative, joined: *[bun.MAX_PATH_BYTES]u8, non_normalized_path: string) [2]string {
var abs: string = "";
@@ -69,16 +88,22 @@ pub const FolderResolution = union(Tag) {
} else {
var remain: []u8 = joined[0..];
switch (global_or_relative) {
- .global => |path| {
- const offset = path.len - @as(usize, @boolToInt(path[path.len - 1] == std.fs.path.sep));
- @memcpy(remain.ptr, path.ptr, offset);
- remain = remain[offset..];
- if ((path[path.len - 1] != std.fs.path.sep) and (normalized[0] != std.fs.path.sep)) {
- remain[0] = std.fs.path.sep;
- remain = remain[1..];
+ .global, .cache_folder => {
+ const path = if (global_or_relative == .global) global_or_relative.global else global_or_relative.cache_folder;
+ if (path.len > 0) {
+ const offset = path.len -| @as(usize, @boolToInt(path[path.len -| 1] == std.fs.path.sep));
+ if (offset > 0)
+ @memcpy(remain.ptr, path.ptr, offset);
+ remain = remain[offset..];
+ if (normalized.len > 0) {
+ if ((path[path.len - 1] != std.fs.path.sep) and (normalized[0] != std.fs.path.sep)) {
+ remain[0] = std.fs.path.sep;
+ remain = remain[1..];
+ }
+ }
}
},
- .relative => {},
+ else => {},
}
std.mem.copy(u8, remain, normalized);
remain[normalized.len] = std.fs.path.sep;
@@ -136,6 +161,7 @@ pub const FolderResolution = union(Tag) {
pub const GlobalOrRelative = union(enum) {
global: []const u8,
relative: void,
+ cache_folder: []const u8,
};
pub fn getOrPut(global_or_relative: GlobalOrRelative, version: Dependency.Version, non_normalized_path: string, manager: *PackageManager) FolderResolution {
@@ -149,7 +175,7 @@ pub const FolderResolution = union(Tag) {
joined[abs.len] = 0;
var joinedZ: [:0]u8 = joined[0..abs.len :0];
- const package = switch (global_or_relative) {
+ const package: Lockfile.Package = switch (global_or_relative) {
.global => readPackageJSONFromDisk(
manager,
joinedZ,
@@ -168,6 +194,15 @@ pub const FolderResolution = union(Tag) {
Resolver,
Resolver{ .folder_path = rel },
),
+ .cache_folder => readPackageJSONFromDisk(
+ manager,
+ joinedZ,
+ abs,
+ version,
+ Features.npm,
+ CacheFolderResolver,
+ CacheFolderResolver{ .version = version.value.npm.toVersion() },
+ ),
} catch |err| {
if (err == error.FileNotFound) {
entry.value_ptr.* = .{ .err = error.MissingPackageJSON };
diff --git a/src/install/semver.zig b/src/install/semver.zig
index 8e06b78ce..6d376c875 100644
--- a/src/install/semver.zig
+++ b/src/install/semver.zig
@@ -80,6 +80,34 @@ pub const String = extern struct {
}
}
+ pub const HashContext = struct {
+ a_buf: []const u8,
+ b_buf: []const u8,
+
+ pub fn eql(ctx: HashContext, a: String, b: String) bool {
+ return a.eql(b, ctx.a_buf, ctx.b_buf);
+ }
+
+ pub fn hash(ctx: HashContext, a: String) u64 {
+ const str = a.slice(ctx.a_buf);
+ return bun.hash(str);
+ }
+ };
+
+ pub const ArrayHashContext = struct {
+ a_buf: []const u8,
+ b_buf: []const u8,
+
+ pub fn eql(ctx: ArrayHashContext, a: String, b: String, _: usize) bool {
+ return a.eql(b, ctx.a_buf, ctx.b_buf);
+ }
+
+ pub fn hash(ctx: ArrayHashContext, a: String) u32 {
+ const str = a.slice(ctx.a_buf);
+ return @truncate(u32, bun.hash(str));
+ }
+ };
+
pub fn init(
buf: string,
in: string,
@@ -267,9 +295,44 @@ pub const String = extern struct {
return @call(.{ .modifier = .always_inline }, appendWithHash, .{ this, Type, slice_, stringHash(slice_) });
}
+ pub fn appendUTF8WithoutPool(this: *Builder, comptime Type: type, slice_: string, hash: u64) Type {
+ if (slice_.len <= String.max_inline_len) {
+ if (strings.isAllASCII(slice_)) {
+ switch (Type) {
+ String => {
+ return String.init(this.allocatedSlice(), slice_);
+ },
+ ExternalString => {
+ return ExternalString.init(this.allocatedSlice(), slice_, hash);
+ },
+ else => @compileError("Invalid type passed to StringBuilder"),
+ }
+ }
+ }
+
+ assert(this.len <= this.cap); // didn't count everything
+ assert(this.ptr != null); // must call allocate first
+
+ copy(u8, this.ptr.?[this.len..this.cap], slice_);
+ const final_slice = this.ptr.?[this.len..this.cap][0..slice_.len];
+ this.len += slice_.len;
+
+ assert(this.len <= this.cap);
+
+ switch (Type) {
+ String => {
+ return String.init(this.allocatedSlice(), final_slice);
+ },
+ ExternalString => {
+ return ExternalString.init(this.allocatedSlice(), final_slice, hash);
+ },
+ else => @compileError("Invalid type passed to StringBuilder"),
+ }
+ }
+
// SlicedString is not supported due to inline strings.
pub fn appendWithoutPool(this: *Builder, comptime Type: type, slice_: string, hash: u64) Type {
- if (slice_.len < String.max_inline_len) {
+ if (slice_.len <= String.max_inline_len) {
switch (Type) {
String => {
return String.init(this.allocatedSlice(), slice_);
@@ -301,7 +364,7 @@ pub const String = extern struct {
}
pub fn appendWithHash(this: *Builder, comptime Type: type, slice_: string, hash: u64) Type {
- if (slice_.len < String.max_inline_len) {
+ if (slice_.len <= String.max_inline_len) {
switch (Type) {
String => {
return String.init(this.allocatedSlice(), slice_);
@@ -490,6 +553,15 @@ pub const Version = extern struct {
tag: Tag = Tag{},
// raw: RawType = RawType{},
+ /// Assumes that there is only one buffer for all the strings
+ pub fn sortGt(ctx: []const u8, lhs: Version, rhs: Version) bool {
+ return orderFn(ctx, lhs, rhs) == .gt;
+ }
+
+ pub fn orderFn(ctx: []const u8, lhs: Version, rhs: Version) std.math.Order {
+ return lhs.order(rhs, ctx, ctx);
+ }
+
pub fn cloneInto(this: Version, slice: []const u8, buf: *[]u8) Version {
return Version{
.major = this.major,
@@ -622,7 +694,7 @@ pub const Version = extern struct {
}
if (this.build.isInline()) {
- build = this.pre.build;
+ build = this.build.value;
} else {
const build_slice = this.build.slice(slice);
std.mem.copy(u8, buf.*, build_slice);
@@ -636,7 +708,7 @@ pub const Version = extern struct {
.hash = this.pre.hash,
},
.build = .{
- .value = this.build,
+ .value = build,
.hash = this.build.hash,
},
};
@@ -1202,6 +1274,37 @@ pub const Query = struct {
pub const build = 0;
};
+ pub fn deinit(this: *Group) void {
+ var list = this.head;
+ var allocator = this.allocator;
+
+ while (list.next) |next| {
+ var query = list.head;
+ while (query.next) |next_query| {
+ allocator.destroy(next_query);
+ query = next_query.*;
+ }
+ allocator.destroy(next);
+ list = next.*;
+ }
+ }
+
+ pub fn from(version: Version) Group {
+ return .{
+ .allocator = bun.default_allocator,
+ .head = .{
+ .head = .{
+ .range = .{
+ .left = .{
+ .op = .eql,
+ .version = version,
+ },
+ },
+ },
+ },
+ };
+ }
+
pub const FlagsBitSet = std.bit_set.IntegerBitSet(3);
pub fn isExact(this: *const Group) bool {
@@ -1212,6 +1315,11 @@ pub const Query = struct {
return lhs.head.eql(&rhs.head);
}
+ pub fn toVersion(this: Group) Version {
+ std.debug.assert(this.isExact() or this.head.head.range.left.op == .unset);
+ return this.head.head.range.left.version;
+ }
+
pub fn orVersion(self: *Group, version: Version) !void {
if (self.tail == null and !self.head.head.range.hasLeft()) {
self.head.head.range.left.version = version;