aboutsummaryrefslogtreecommitdiff
path: root/src/install/install.zig
diff options
context:
space:
mode:
Diffstat (limited to 'src/install/install.zig')
-rw-r--r--src/install/install.zig276
1 files changed, 212 insertions, 64 deletions
diff --git a/src/install/install.zig b/src/install/install.zig
index 6bb8ea5f0..f2d484457 100644
--- a/src/install/install.zig
+++ b/src/install/install.zig
@@ -339,7 +339,7 @@ pub const Features = struct {
optional_dependencies: bool = false,
dev_dependencies: bool = false,
scripts: bool = false,
- peer_dependencies: bool = false,
+ peer_dependencies: bool = true,
is_main: bool = false,
check_for_duplicate_dependencies: bool = false,
@@ -485,49 +485,66 @@ pub const Lockfile = struct {
try new.packages.ensureTotalCapacity(old.allocator, old.packages.len);
try new.buffers.preallocate(old.buffers, old.allocator);
+ const InstallOrder = struct {
+ parent: PackageID,
+ children: PackageIDSlice,
+ };
+
old.scratch.dependency_list_queue.head = 0;
// Step 1. Recreate the lockfile with only the packages that are still alive
const root = old.rootPackage() orelse return error.NoPackage;
- var slices = old.packages.slice();
var package_id_mapping = try old.allocator.alloc(PackageID, old.packages.len);
std.mem.set(
PackageID,
package_id_mapping,
invalid_package_id,
);
- var clone_queue_ = PendingResolutions.init(old.allocator);
- var clone_queue = &clone_queue_;
- try clone_queue.ensureUnusedCapacity(root.dependencies.len);
+ var clone_queue_ = PendingResolutions.init();
+ var cloner = Cloner{
+ .old = old,
+ .lockfile = new,
+ .mapping = package_id_mapping,
+ .clone_queue = clone_queue_,
+ };
+ // try clone_queue.ensureUnusedCapacity(root.dependencies.len);
- var duplicate_resolutions_bitset = try std.DynamicBitSetUnmanaged.initEmpty(old.buffers.resolutions.items.len, old.allocator);
- var duplicate_resolutions_bitset_ptr = &duplicate_resolutions_bitset;
- _ = try root.clone(old, new, package_id_mapping, clone_queue, duplicate_resolutions_bitset_ptr);
+ _ = try root.clone(old, new, package_id_mapping, &cloner);
+ try cloner.flush();
- while (clone_queue.readItem()) |to_clone_| {
- const to_clone: PendingResolution = to_clone_;
+ return new;
+ }
- const mapping = package_id_mapping[to_clone.old_resolution];
- if (mapping < max_package_id) {
- new.buffers.resolutions.items[to_clone.resolve_id] = package_id_mapping[to_clone.old_resolution];
+ const Cloner = struct {
+ clone_queue: PendingResolutions,
+ lockfile: *Lockfile,
+ old: *Lockfile,
+ mapping: []PackageID,
- continue;
- }
+ pub fn flush(this: *Cloner) anyerror!void {
+ const max_package_id = this.old.packages.len;
+ while (this.clone_queue.readItem()) |to_clone_| {
+ const to_clone: PendingResolution = to_clone_;
- const old_package = old.packages.get(to_clone.old_resolution);
+ const mapping = this.mapping[to_clone.old_resolution];
+ if (mapping < max_package_id) {
+ this.lockfile.buffers.resolutions.items[to_clone.resolve_id] = this.mapping[to_clone.old_resolution];
- new.buffers.resolutions.items[to_clone.resolve_id] = try old_package.clone(
- old,
- new,
- package_id_mapping,
- clone_queue,
- duplicate_resolutions_bitset_ptr,
- );
- }
+ continue;
+ }
- return new;
- }
+ const old_package = this.old.packages.get(to_clone.old_resolution);
+
+ this.lockfile.buffers.resolutions.items[to_clone.resolve_id] = try old_package.clone(
+ this.old,
+ this.lockfile,
+ this.mapping,
+ this,
+ );
+ }
+ }
+ };
const PendingResolution = struct {
old_resolution: PackageID,
@@ -535,7 +552,7 @@ pub const Lockfile = struct {
parent: PackageID,
};
- const PendingResolutions = std.fifo.LinearFifo(PendingResolution, .Dynamic);
+ const PendingResolutions = std.fifo.LinearFifo(PendingResolution, .{ .Static = 32 });
pub const Printer = struct {
lockfile: *Lockfile,
@@ -657,6 +674,60 @@ pub const Lockfile = struct {
}
}
+ pub const Tree = struct {
+ pub fn print(
+ this: *Printer,
+ comptime Writer: type,
+ writer: Writer,
+ comptime enable_ansi_colors: bool,
+ ) !void {
+ var lockfile = this.lockfile;
+
+ const IDDepthPair = struct {
+ depth: u16 = 0,
+ id: PackageID,
+ };
+
+ var visited = try std.DynamicBitSetUnmanaged.initEmpty(this.lockfile.packages.len, this.lockfile.allocator);
+
+ var slice = this.lockfile.packages.slice();
+ const names: []const String = slice.items(.name);
+ const resolved: []const Resolution = slice.items(.resolution);
+ const metas: []const Lockfile.Package.Meta = slice.items(.meta);
+ if (names.len == 0) return;
+ const dependency_lists = slice.items(.dependencies);
+ const resolutions_list = slice.items(.resolutions);
+ const resolutions_buffer = this.lockfile.buffers.resolutions.items;
+ const dependencies_buffer = this.lockfile.buffers.dependencies.items;
+ const package_count = @truncate(PackageID, names.len);
+ const string_buf = this.lockfile.buffers.string_bytes.items;
+
+ const root = this.lockfile.rootPackage() orelse return;
+ visited.set(0);
+
+ for (names) |name, package_id| {
+ const package_name = name.slice(string_buf);
+
+ const dependency_list = dependency_lists[package_id];
+
+ try writer.print(
+ comptime Output.prettyFmt(" <r><b>{s}<r><d>@<b>{}<r><d> ({d} dependencies)<r>\n", enable_ansi_colors),
+ .{
+ package_name,
+ resolved[package_id].fmt(string_buf),
+ dependency_list.len,
+ },
+ );
+
+ if (visited.isSet(package_id)) {
+ continue;
+ }
+
+ visited.set(package_id);
+ }
+ }
+ };
+
pub const Yarn = struct {
pub fn print(
this: *Printer,
@@ -944,10 +1015,12 @@ pub const Lockfile = struct {
pub fn getPackageID(
this: *Lockfile,
name_hash: u64,
+ // if it's a peer dependency
+ version: ?Dependency.Version,
resolution: Resolution,
) ?PackageID {
const entry = this.package_index.get(name_hash) orelse return null;
- const resolutions = this.packages.items(.resolution);
+ const resolutions: []const Resolution = this.packages.items(.resolution);
switch (entry) {
.PackageID => |id| {
if (comptime Environment.isDebug or Environment.isTest) {
@@ -961,10 +1034,23 @@ pub const Lockfile = struct {
this.buffers.string_bytes.items,
)) {
return id;
+ } else if (version) |version_| {
+ switch (version_.tag) {
+ .npm => {
+ // is it a peerDependency satisfied by a parent package?
+ if (version_.value.npm.satisfies(resolutions[id].value.npm)) {
+ return id;
+ }
+ },
+ else => return null,
+ }
}
},
.PackageIDMultiple => |multi_| {
const multi = std.mem.span(multi_);
+
+ const can_satisfy = version != null and version.?.tag == .npm;
+
for (multi) |id| {
if (comptime Environment.isDebug or Environment.isTest) {
std.debug.assert(id != invalid_package_id);
@@ -975,6 +1061,10 @@ pub const Lockfile = struct {
if (resolutions[id].eql(resolution, this.buffers.string_bytes.items, this.buffers.string_bytes.items)) {
return id;
}
+
+ if (can_satisfy and version.?.value.npm.satisfies(resolutions[id].value.npm)) {
+ return id;
+ }
}
},
}
@@ -1045,8 +1135,8 @@ pub const Lockfile = struct {
pub fn appendPackageWithID(this: *Lockfile, package_: Lockfile.Package, id: PackageID) !Lockfile.Package {
defer {
if (comptime Environment.isDebug) {
- std.debug.assert(this.getPackageID(package_.name_hash, package_.resolution) != null);
- std.debug.assert(this.getPackageID(package_.name_hash, package_.resolution).? == id);
+ std.debug.assert(this.getPackageID(package_.name_hash, null, package_.resolution) != null);
+ std.debug.assert(this.getPackageID(package_.name_hash, null, package_.resolution).? == id);
}
}
var package = package_;
@@ -1227,11 +1317,19 @@ pub const Lockfile = struct {
const DependencySlice = ExternalSlice(Dependency);
const PackageIDSlice = ExternalSlice(PackageID);
+ const NodeModulesFolderSlice = ExternalSlice(NodeModulesFolder);
+
const PackageIDList = std.ArrayListUnmanaged(PackageID);
const DependencyList = std.ArrayListUnmanaged(Dependency);
const StringBuffer = std.ArrayListUnmanaged(u8);
const SmallExternalStringBuffer = std.ArrayListUnmanaged(String);
+ const NodeModulesFolder = extern struct {
+ in: PackageID = 0,
+ packages: PackageIDSlice = PackageIDSlice{},
+ children: NodeModulesFolderSlice = NodeModulesFolderSlice{},
+ };
+
pub const Package = extern struct {
const DependencyGroup = struct {
prop: string,
@@ -1267,8 +1365,7 @@ pub const Lockfile = struct {
old: *Lockfile,
new: *Lockfile,
package_id_mapping: []PackageID,
- clone_queue: *PendingResolutions,
- duplicate_resolutions_bitset: *std.DynamicBitSetUnmanaged,
+ cloner: *Cloner,
) !PackageID {
const old_string_buf = old.buffers.string_bytes.items;
var builder_ = new.stringBuilder();
@@ -1280,6 +1377,7 @@ pub const Lockfile = struct {
const old_dependencies: []const Dependency = this.dependencies.get(old.buffers.dependencies.items);
const old_resolutions: []const PackageID = this.resolutions.get(old.buffers.resolutions.items);
+
for (old_dependencies) |dependency, i| {
dependency.count(old_string_buf, *Lockfile.StringBuilder, builder);
}
@@ -1292,7 +1390,7 @@ pub const Lockfile = struct {
const prev_len = @truncate(u32, new.buffers.dependencies.items.len);
const end = prev_len + @truncate(u32, old_dependencies.len);
- const max_package_id = @truncate(u32, old.packages.len);
+ const max_package_id = @truncate(PackageID, old.packages.len);
new.buffers.dependencies.items = new.buffers.dependencies.items.ptr[0..end];
new.buffers.resolutions.items = new.buffers.resolutions.items.ptr[0..end];
@@ -1333,29 +1431,32 @@ pub const Lockfile = struct {
*Lockfile.StringBuilder,
builder,
);
+ }
- const old_resolution = old_resolutions[i];
- if (old_resolution < max_package_id) {
- const mapped = package_id_mapping[old_resolution];
- const resolve_id = new_package.resolutions.off + @truncate(u32, i);
+ builder.clamp();
- if (!old.unique_packages.isSet(old_resolution)) duplicate_resolutions_bitset.set(resolve_id);
+ for (old_resolutions) |old_resolution, i| {
+ if (old_resolution >= max_package_id) continue;
- if (mapped < max_package_id) {
- resolutions[i] = mapped;
- } else {
- try clone_queue.writeItem(
- PendingResolution{
- .old_resolution = old_resolution,
- .parent = new_package.meta.id,
- .resolve_id = resolve_id,
- },
- );
- }
+ if (cloner.clone_queue.writableLength() == 0) {
+ try cloner.flush();
}
- }
- builder.clamp();
+ const mapped = package_id_mapping[old_resolution];
+ const resolve_id = new_package.resolutions.off + @intCast(PackageID, i);
+
+ if (mapped < max_package_id) {
+ resolutions[i] = mapped;
+ } else {
+ cloner.clone_queue.writeItemAssumeCapacity(
+ PendingResolution{
+ .old_resolution = old_resolution,
+ .parent = new_package.meta.id,
+ .resolve_id = resolve_id,
+ },
+ );
+ }
+ }
return new_package.meta.id;
}
@@ -1466,16 +1567,21 @@ pub const Lockfile = struct {
const version_strings = map.value.get(manifest.external_strings_for_versions);
if (comptime Environment.isDebug) std.debug.assert(keys.len == version_strings.len);
+ const is_peer = comptime strings.eqlComptime(group.field, "peer_dependencies");
for (keys) |key, i| {
const version_string_ = version_strings[i];
const name: ExternalString = string_builder.appendWithHash(ExternalString, key.slice(string_buf), key.hash);
const dep_version = string_builder.appendWithHash(String, version_string_.slice(string_buf), version_string_.hash);
const sliced = dep_version.sliced(lockfile.buffers.string_bytes.items);
+
const dependency = Dependency{
.name = name.value,
.name_hash = name.hash,
- .behavior = group.behavior,
+ .behavior = if (comptime is_peer)
+ group.behavior.setOptional(package_version.optional_peer_dependencies_len > i)
+ else
+ group.behavior,
.version = Dependency.parse(
allocator,
sliced.slice,
@@ -1983,6 +2089,8 @@ pub const Lockfile = struct {
resolutions: PackageIDList = PackageIDList{},
dependencies: DependencyList = DependencyList{},
extern_strings: SmallExternalStringBuffer = SmallExternalStringBuffer{},
+ // node_modules_folders: NodeModulesFolderList = NodeModulesFolderList{},
+ // node_modules_package_ids: PackageIDList = PackageIDList{},
string_bytes: StringBuffer = StringBuffer{},
pub fn preallocate(this: *Buffers, that: Buffers, allocator: *std.mem.Allocator) !void {
@@ -2941,15 +3049,20 @@ pub const PackageManager = struct {
name: String,
version: Dependency.Version,
dependency_id: PackageID,
+ is_peer: bool,
manifest: *const Npm.PackageManifest,
find_result: Npm.PackageManifest.FindResult,
) !?ResolvedPackageResult {
// Was this package already allocated? Let's reuse the existing one.
- if (this.lockfile.getPackageID(name_hash, .{
- .tag = .npm,
- .value = .{ .npm = find_result.version },
- })) |id| {
+ if (this.lockfile.getPackageID(
+ name_hash,
+ version,
+ .{
+ .tag = .npm,
+ .value = .{ .npm = find_result.version },
+ },
+ )) |id| {
return ResolvedPackageResult{
.package = this.lockfile.packages.get(id),
.is_first_time = false,
@@ -3105,6 +3218,7 @@ pub const PackageManager = struct {
name_hash: PackageNameHash,
name: String,
version: Dependency.Version,
+ is_peer: bool,
dependency_id: PackageID,
resolution: PackageID,
) !?ResolvedPackageResult {
@@ -3126,7 +3240,7 @@ pub const PackageManager = struct {
else => unreachable,
};
- return try getOrPutResolvedPackageWithFindResult(this, name_hash, name, version, dependency_id, manifest, find_result);
+ return try getOrPutResolvedPackageWithFindResult(this, name_hash, name, version, dependency_id, is_peer, manifest, find_result);
},
else => return null,
@@ -3259,7 +3373,14 @@ pub const PackageManager = struct {
switch (dependency.version.tag) {
.npm, .dist_tag => {
retry_from_manifests_ptr: while (true) {
- var resolve_result_ = this.getOrPutResolvedPackage(name_hash, name, version, id, resolution);
+ var resolve_result_ = this.getOrPutResolvedPackage(
+ name_hash,
+ name,
+ version,
+ dependency.behavior.isPeer(),
+ id,
+ resolution,
+ );
retry_with_new_resolve_result: while (true) {
const resolve_result = resolve_result_ catch |err| {
@@ -3327,7 +3448,7 @@ pub const PackageManager = struct {
this.enqueueNetworkTask(network_task);
}
}
- } else {
+ } else if (!dependency.behavior.isPeer()) {
const task_id = Task.Id.forManifest(Task.Tag.package_manifest, this.lockfile.str(name));
var network_entry = try this.network_dedupe_map.getOrPutContext(this.allocator, task_id, .{});
if (!network_entry.found_existing) {
@@ -3349,6 +3470,7 @@ pub const PackageManager = struct {
name,
version,
id,
+ dependency.behavior.isPeer(),
&loaded_manifest.?,
find_result,
) catch null) |new_resolve_result| {
@@ -4921,7 +5043,6 @@ pub const PackageManager = struct {
const cache_dir = this.cache_directory;
lockfile.unique_packages.unset(0);
- var toplevel_node_modules = lockfile.unique_packages.iterator(.{});
// If there was already a valid lockfile and so we did not resolve, i.e. there was zero network activity
// the packages could still not be in the cache dir
@@ -4953,12 +5074,29 @@ pub const PackageManager = struct {
var summary = PackageInstall.Summary{};
{
- const toplevel_count = lockfile.unique_packages.count();
-
var parts = lockfile.packages.slice();
var metas = parts.items(.meta);
var names = parts.items(.name);
+ var dependency_lists = parts.items(.dependencies);
+ var dependencies = lockfile.buffers.dependencies.items;
+ var resolutions_buffer = lockfile.buffers.resolutions.items;
+ var resolution_lists = parts.items(.resolutions);
var resolutions = parts.items(.resolution);
+
+ const pending_task_offset = this.total_tasks;
+ const root_dependency_list = dependency_lists[0];
+ const root_resolution_list = resolution_lists[0];
+
+ var toplevel_packages = try lockfile.unique_packages.clone(this.allocator);
+ const max_package_id = @truncate(PackageID, names.len);
+ for (root_resolution_list.get(resolutions_buffer)) |package_id| {
+ if (package_id > max_package_id) continue;
+ toplevel_packages.set(package_id);
+ }
+
+ const toplevel_count = toplevel_packages.count();
+ var toplevel_node_modules = toplevel_packages.iterator(.{});
+
var installer = PackageInstaller{
.manager = this,
.metas = metas,
@@ -4975,8 +5113,6 @@ pub const PackageManager = struct {
.install_count = toplevel_count,
};
- const pending_task_offset = this.total_tasks;
-
// When it's a Good Idea, run the install in single-threaded
// From benchmarking, apfs clonefile() is ~6x faster than copyfile() on macOS
// Running it in parallel is the same or slower.
@@ -5389,6 +5525,18 @@ pub const PackageManager = struct {
}
}
+ if (manager.options.log_level != .silent) {
+ var printer = Lockfile.Printer{
+ .lockfile = manager.lockfile,
+ .options = manager.options,
+ };
+ if (Output.enable_ansi_colors) {
+ try Lockfile.Printer.Tree.print(&printer, Output.WriterType, Output.writer(), true);
+ } else {
+ try Lockfile.Printer.Tree.print(&printer, Output.WriterType, Output.writer(), false);
+ }
+ }
+
Output.prettyln(" <green>+{d}<r> add | <cyan>{d}<r> update | <r><red>-{d}<r> remove | {d} installed | {d} deduped | {d} skipped | {d} failed", .{
manager.summary.add,
manager.summary.update,