Compare commits

...

1 Commits

Author SHA1 Message Date
Claude Bot
8b1f5dd5b3 fix(install): deduplicate "Skip installing" verbose messages for os/cpu mismatch
When running `bun install --verbose`, the "Skip installing <pkg> - os mismatch"
message was printed once per dependency tree encounter with no deduplication.
In projects with many transitive dependencies on packages like `chokidar`
(which optionally depends on `fsevents`), this resulted in many identical
messages flooding the terminal.

Track which package IDs have already had their skip message printed using a
bitset, so each message is printed at most once.

Closes #26957

Co-Authored-By: Claude <noreply@anthropic.com>
2026-02-12 05:23:15 +00:00
7 changed files with 127 additions and 8 deletions

View File

@@ -51,6 +51,9 @@ pub fn installIsolatedPackages(
var early_dedupe: std.AutoHashMap(PackageID, Store.Node.Id) = .init(lockfile.allocator);
defer early_dedupe.deinit();
var logged_disabled_pkgs = try bun.bit_set.DynamicBitSetUnmanaged.initEmpty(lockfile.allocator, lockfile.packages.len);
defer logged_disabled_pkgs.deinit(lockfile.allocator);
var peer_dep_ids: std.array_list.Managed(DependencyID) = .init(lockfile.allocator);
defer peer_dep_ids.deinit();
@@ -223,6 +226,7 @@ pub fn installIsolatedPackages(
install_root_dependencies,
manager,
lockfile,
&logged_disabled_pkgs,
)) {
continue;
}

View File

@@ -945,6 +945,7 @@ pub fn hoist(
.workspace_filters = workspace_filters,
.packages_to_install = packages_to_install,
.pending_optional_peers = .init(allocator),
.logged_disabled_pkgs = if (method == .filter) try bun.bit_set.DynamicBitSetUnmanaged.initEmpty(allocator, lockfile.packages.len) else {},
};
try (Tree{}).processSubtree(

View File

@@ -257,6 +257,9 @@ pub fn Builder(comptime method: BuilderMethod) type {
workspace_filters: if (method == .filter) []const WorkspaceFilter else void = if (method == .filter) &.{},
install_root_dependencies: if (method == .filter) bool else void,
packages_to_install: if (method == .filter) ?[]const PackageID else void,
/// Tracks which packages have already had their "Skip installing" message
/// printed, to avoid duplicate verbose messages for the same package.
logged_disabled_pkgs: if (method == .filter) Bitset else void = if (method == .filter) .{} else {},
pub fn maybeReportError(this: *@This(), comptime fmt: string, args: anytype) void {
this.log.addErrorFmt(null, logger.Loc.Empty, this.allocator, fmt, args) catch {};
@@ -321,6 +324,7 @@ pub fn Builder(comptime method: BuilderMethod) type {
this.queue.deinit();
this.sort_buf.deinit(this.allocator);
this.pending_optional_peers.deinit();
if (method == .filter) this.logged_disabled_pkgs.deinit(this.allocator);
// take over the `builder.list` pointer for only trees
if (@intFromPtr(trees.ptr) != @intFromPtr(list_ptr)) {
@@ -344,6 +348,7 @@ pub fn isFilteredDependencyOrWorkspace(
install_root_dependencies: bool,
manager: *const PackageManager,
lockfile: *const Lockfile,
logged_disabled_pkgs: *Bitset,
) bool {
const pkg_id = lockfile.buffers.resolutions.items[dep_id];
if (pkg_id >= lockfile.packages.len) {
@@ -365,14 +370,17 @@ pub fn isFilteredDependencyOrWorkspace(
if (pkg_metas[pkg_id].isDisabled(manager.options.cpu, manager.options.os)) {
if (manager.options.log_level.isVerbose()) {
const meta = &pkg_metas[pkg_id];
const name = lockfile.str(&pkg_names[pkg_id]);
if (!meta.os.isMatch(manager.options.os) and !meta.arch.isMatch(manager.options.cpu)) {
Output.prettyErrorln("<d>Skip installing<r> <b>{s}<r> <d>- cpu & os mismatch<r>", .{name});
} else if (!meta.os.isMatch(manager.options.os)) {
Output.prettyErrorln("<d>Skip installing<r> <b>{s}<r> <d>- os mismatch<r>", .{name});
} else if (!meta.arch.isMatch(manager.options.cpu)) {
Output.prettyErrorln("<d>Skip installing<r> <b>{s}<r> <d>- cpu mismatch<r>", .{name});
if (!logged_disabled_pkgs.isSet(pkg_id)) {
logged_disabled_pkgs.set(pkg_id);
const meta = &pkg_metas[pkg_id];
const name = lockfile.str(&pkg_names[pkg_id]);
if (!meta.os.isMatch(manager.options.os) and !meta.arch.isMatch(manager.options.cpu)) {
Output.prettyErrorln("<d>Skip installing<r> <b>{s}<r> <d>- cpu & os mismatch<r>", .{name});
} else if (!meta.os.isMatch(manager.options.os)) {
Output.prettyErrorln("<d>Skip installing<r> <b>{s}<r> <d>- os mismatch<r>", .{name});
} else if (!meta.arch.isMatch(manager.options.cpu)) {
Output.prettyErrorln("<d>Skip installing<r> <b>{s}<r> <d>- cpu mismatch<r>", .{name});
}
}
}
return true;
@@ -509,6 +517,7 @@ pub fn processSubtree(
builder.install_root_dependencies,
builder.manager,
builder.lockfile,
&builder.logged_disabled_pkgs,
)) {
continue;
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,105 @@
import { spawn } from "bun";
import { afterAll, afterEach, beforeAll, beforeEach, expect, it } from "bun:test";
import { writeFile } from "fs/promises";
import { bunEnv, bunExe } from "harness";
import { basename, join } from "path";
import {
dummyAfterAll,
dummyAfterEach,
dummyBeforeAll,
dummyBeforeEach,
package_dir,
root_url,
setHandler,
} from "../../cli/install/dummy.registry.js";
beforeAll(dummyBeforeAll);
afterAll(dummyAfterAll);
beforeEach(async () => {
await dummyBeforeEach({ linker: "hoisted" });
});
afterEach(dummyAfterEach);
it("should deduplicate 'Skip installing' verbose messages for os/cpu mismatch", async () => {
// Custom handler that returns different metadata per package:
// - pkg-a and pkg-b are normal packages that depend on os-restricted-dep
// - os-restricted-dep has os: ["darwin"], so it will be skipped on non-darwin
setHandler(async request => {
const url = request.url.replaceAll("%2f", "/");
if (url.endsWith(".tgz")) {
return new Response(Bun.file(join(import.meta.dir, "../../cli/install", basename(url).toLowerCase())));
}
expect(request.method).toBe("GET");
const name = url.slice(url.indexOf("/", root_url.length) + 1);
if (name === "os-restricted-dep") {
return new Response(
JSON.stringify({
name: "os-restricted-dep",
versions: {
"1.0.0": {
name: "os-restricted-dep",
version: "1.0.0",
os: ["darwin"],
dist: { tarball: `${root_url}/os-restricted-dep-1.0.0.tgz` },
},
},
"dist-tags": { latest: "1.0.0" },
}),
);
}
// For pkg-a and pkg-b: they both optionally depend on os-restricted-dep
return new Response(
JSON.stringify({
name,
versions: {
"1.0.0": {
name,
version: "1.0.0",
optionalDependencies: {
"os-restricted-dep": "1.0.0",
},
dist: { tarball: `${root_url}/${name}-1.0.0.tgz` },
},
},
"dist-tags": { latest: "1.0.0" },
}),
);
});
await writeFile(
join(package_dir, "package.json"),
JSON.stringify({
name: "test-dedup-skip-messages",
version: "1.0.0",
dependencies: {
"pkg-a": "1.0.0",
"pkg-b": "1.0.0",
},
}),
);
// Install with --os linux --verbose to trigger "Skip installing" for the darwin-only dep
const { stdout, stderr, exited } = spawn({
cmd: [bunExe(), "install", "--os", "linux", "--verbose"],
cwd: package_dir,
env: bunEnv,
stdout: "pipe",
stderr: "pipe",
});
const [stdoutText, stderrText, exitCode] = await Promise.all([stdout.text(), stderr.text(), exited]);
const combined = stdoutText + stderrText;
const skipMessages = combined.split("\n").filter((line: string) => line.includes("Skip installing"));
// The "Skip installing os-restricted-dep" message should appear exactly once,
// not once per parent that depends on it.
expect(skipMessages.length).toBe(1);
expect(skipMessages[0]).toContain("os-restricted-dep");
expect(skipMessages[0]).toContain("os mismatch");
expect(exitCode).toBe(0);
});