Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions docs/pm/cli/install.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,8 @@ bun install --offline

`--prefer-offline` is also what `install.prefer = "offline"` in `bunfig.toml` selects; `install.offline = true` is the config form of `--offline`. With a complete restored cache, `--offline --frozen-lockfile` makes a CI install fully deterministic and network-free; `--prefer-offline` still fetches whatever the cache is missing.

An install is only certain to cache the packages that it places in `node_modules`. To warm the cache for an `--offline` install, run an install on the same platform that places at least the same packages. An install with `--production` or `--omit` does not cache the groups it leaves out.

See [lockfile](/pm/lockfile) for more on `bun.lock`.

---
Expand Down
1 change: 1 addition & 0 deletions src/install/PackageManager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2497,6 +2497,7 @@ fn init_with_runtime_once(
max_concurrent_lifecycle_scripts: cli
.concurrent_scripts
.unwrap_or((cpu_count * 2) as usize),
runtime_auto_install: true,
..Default::default()
}
);
Expand Down
17 changes: 17 additions & 0 deletions src/install/PackageManager/PackageManagerEnqueue.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2417,6 +2417,17 @@ fn get_or_put_resolved_package_with_find_result(
let this: &mut PackageManager = unsafe { &mut *guard.0 };
// The scopeguard runs on ALL exits, never disarmed.

// `remote_package_features` only lacks groups that no remote package has.
let placed = behavior.is_placed(this.options.local_package_features);
// `is_filtered_dependency_or_workspace` filters this package and everything below it.
let unplaced = !this.options.runtime_auto_install
&& (!placed
|| package.is_disabled(this.options.cpu, this.options.os)
|| this.lockfile.is_in_unplaced_subtree(dependency_id));
if unplaced {
this.lockfile.mark_unplaced_subtree(package.dependencies);
}

// non-null if the package is in "patchedDependencies"
let mut name_and_version_hash: Option<u64> = None;
let mut patchfile_hash: Option<u64> = None;
Expand All @@ -2434,6 +2445,12 @@ fn get_or_put_resolved_package_with_find_result(
is_first_time: true,
task: None,
}),
// If a placed dependency needs it too, the install phase fetches and patches it.
_ if unplaced => Some(ResolvedPackageResult {
package,
is_first_time: true,
task: None,
}),
// Do we need to download the tarball?
install::PreinstallState::Extract => 'extract: {
// Skip tarball download when prefetch_resolved_tarballs is disabled (e.g., --lockfile-only)
Expand Down
4 changes: 4 additions & 0 deletions src/install/PackageManager/PackageManagerOptions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,9 @@ pub struct Options {

pub(crate) lockfile_only: bool,

/// Set by `init_with_runtime`: no install phase, so only a resolve downloads a package.
pub(crate) runtime_auto_install: bool,

// `bun pm version` command options
pub git_tag_version: bool,
pub allow_same_version: bool,
Expand Down Expand Up @@ -161,6 +164,7 @@ impl Default for Options {
ca_file_name: b"",
save_text_lockfile: None,
lockfile_only: false,
runtime_auto_install: false,
git_tag_version: true,
allow_same_version: false,
preid: b"",
Expand Down
25 changes: 24 additions & 1 deletion src/install/lockfile.rs
Original file line number Diff line number Diff line change
Expand Up @@ -734,7 +734,7 @@ impl Lockfile {

let dep = &self.buffers.dependencies[dep_id as usize];

dep.behavior.is_bundled() || !dep.behavior.is_enabled(features)
!dep.behavior.is_placed(features)
}

pub fn resolve_catalog_dependency(&self, dep: &Dependency) -> Option<DependencyVersion> {
Expand Down Expand Up @@ -2196,6 +2196,26 @@ impl Lockfile {
self.exact_pinned.set(i);
}

/// See `Scratch::unplaced_subtree`.
pub(crate) fn mark_unplaced_subtree(&mut self, dependencies: DependencySlice) {
let range = bun_collections::bit_set::Range {
start: dependencies.begin() as usize,
end: dependencies.end() as usize,
};
let unplaced_subtree = &mut self.scratch.unplaced_subtree;
if unplaced_subtree.bit_length() < range.end {
bun_core::handle_oom(unplaced_subtree.resize(range.end, false));
}
unplaced_subtree.set_range_value(range, true);
}

#[inline]
pub(crate) fn is_in_unplaced_subtree(&self, id: DependencyID) -> bool {
self.scratch
.unplaced_subtree
.is_set_allow_out_of_bound(id as usize, false)
}

pub(crate) fn get_package_id(
&self,
name_hash: u64,
Expand Down Expand Up @@ -2539,6 +2559,8 @@ impl Lockfile {
pub struct Scratch {
pub(crate) duplicate_checker_map: DuplicateCheckerMap,
pub(crate) dependency_list_queue: DependencyQueue,
/// `bit[dependency_id]`: this resolve reached it below a dependency that the installers filter.
pub(crate) unplaced_subtree: DynamicBitSet,
}

pub(crate) type DuplicateCheckerMap =
Expand All @@ -2550,6 +2572,7 @@ impl Scratch {
Scratch {
dependency_list_queue: DependencyQueue::init(),
duplicate_checker_map: DuplicateCheckerMap::default(),
unplaced_subtree: DynamicBitSet::default(),
}
}
}
Expand Down
6 changes: 1 addition & 5 deletions src/install/lockfile/Tree.rs
Original file line number Diff line number Diff line change
Expand Up @@ -590,17 +590,13 @@ pub(crate) fn is_filtered_dependency_or_workspace(
return true;
}

if dep.behavior.is_bundled() {
return true;
}

let dep_features = if parent_res.tag.is_local_package() {
manager.options.local_package_features
} else {
manager.options.remote_package_features
};

if !dep.behavior.is_enabled(dep_features) {
if !dep.behavior.is_placed(dep_features) {
return true;
}

Expand Down
6 changes: 6 additions & 0 deletions src/install_types/resolver_hooks.rs
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,12 @@ impl Behavior {
|| (features.workspaces && self.is_workspace())
}

/// False when the installers filter the dependency, and with it everything below.
#[inline]
pub fn is_placed(self, features: Features) -> bool {
!self.is_bundled() && self.is_enabled(features)
}

pub fn cmp(self, rhs: Self) -> core::cmp::Ordering {
use core::cmp::Ordering::*;
if self == rhs {
Expand Down
Loading
Loading