Skip to content
Open
Show file tree
Hide file tree
Changes from 6 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
1 change: 0 additions & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions src/bundler/BundleThread.rs
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,8 @@ impl<C: CompletionStruct> BundleThread<C> {
let ptr = SendPtr(instance);
let thread = std::thread::Builder::new()
.name("Bundler".into())
// `Bun.build({ compile })` writes its executable here; use bun's usual stack.
.stack_size(bun_threading::thread_pool::DEFAULT_THREAD_STACK_SIZE as usize)
.spawn(move || {
let ptr = ptr;
// SAFETY: caller guarantees `instance` is valid for 'static; `thread_main`
Expand Down
38 changes: 36 additions & 2 deletions src/options_types/compile_target.rs
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,42 @@ pub enum ParseError {
InvalidTarget,
}

/// What downloading a target's executable takes from the env loader, copied out
/// by [`CompileTarget::download_options`] on the thread that owns the loader so
/// the download itself can run on another one.
Comment thread
robobun marked this conversation as resolved.
Outdated
pub struct DownloadOptions {
pub reject_unauthorized: bool,
/// Proxy for the registry URL, with `NO_PROXY` already applied.
pub http_proxy: Option<Box<[u8]>>,
}

impl Default for DownloadOptions {
/// Placeholder until [`CompileTarget::download_options`] fills it in; verifies TLS.
fn default() -> Self {
Self {
reject_unauthorized: true,
http_proxy: None,
}
}
}

impl CompileTarget {
/// Buffer size callers hand to [`Self::to_npm_registry_url`].
pub const REGISTRY_URL_BUF_LEN: usize = 2048;

pub fn download_options(&self, env: &bun_dotenv::Loader) -> DownloadOptions {
let mut url_buf = [0u8; Self::REGISTRY_URL_BUF_LEN];
// A URL that cannot be built fails the download itself, which reports it.
let http_proxy = self.to_npm_registry_url(&mut url_buf).ok().and_then(|url| {
env.get_http_proxy_for(&bun_url::URL::parse(url))
.map(|proxy| Box::from(proxy.href))
});
DownloadOptions {
reject_unauthorized: env.get_tls_reject_unauthorized(),
http_proxy,
}
}

pub(crate) fn eql(&self, other: &CompileTarget) -> bool {
self.os == other.os
&& self.arch == other.arch
Expand Down Expand Up @@ -185,7 +220,6 @@ impl CompileTarget {
&self,
buf: &'a mut PathBuffer,
version_str: &'a ZStr,
_env: &mut bun_dotenv::Loader,
needs_download: &mut bool,
) -> &'a ZStr {
if self.is_default() {
Expand All @@ -206,7 +240,7 @@ impl CompileTarget {
return version_str;
}

// T1 fallback ignores `_env` (full env-override chain lives in bun_install).
// The full cache-dir override chain lives in bun_install; this is its fallback.
let cache_dir = bun_sys::fetch_cache_directory_path();
let dest = path::resolve_path::join_abs_string_buf_z::<path::platform::Auto>(
path::fs::FileSystem::instance().top_level_dir(),
Expand Down
8 changes: 7 additions & 1 deletion src/runtime/api/JSBundler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ use bun_core::Output;
use bun_core::{String as BunString, ZigString};
use bun_jsc::ConcurrentTask::ConcurrentTask;
use bun_jsc::{self as jsc, CallFrame, JSGlobalObject, JSValue, JsError, JsResult};
use bun_options_types::compile_target::CompileTarget;
use bun_options_types::compile_target::{CompileTarget, DownloadOptions};
use bun_options_types::schema::api; // bun.schema.api
use bun_standalone_graph::StandaloneModuleGraph;

Expand Down Expand Up @@ -223,6 +223,8 @@ pub mod js_bundler {

pub struct CompileOptions {
pub(crate) compile_target: CompileTarget,
/// Taken from the VM's env on the calling thread; the download runs on the bundle thread.
pub(crate) download: DownloadOptions,
pub(crate) exec_argv: OwnedString,
pub(crate) executable_path: OwnedString,
pub(crate) windows_hide_console: bool,
Expand All @@ -244,6 +246,7 @@ pub mod js_bundler {
fn default() -> Self {
Self {
compile_target: CompileTarget::default(),
download: DownloadOptions::default(),
exec_argv: OwnedString::default(),
executable_path: OwnedString::default(),
windows_hide_console: false,
Expand Down Expand Up @@ -1197,6 +1200,9 @@ pub mod js_bundler {
let is_standalone_html = this.target == Target::Browser && has_all_html_entrypoints;
if !is_standalone_html {
this.target = Target::Bun;
compile.download = compile
.compile_target
.download_options(global_this.bun_vm().env_loader());

let define_keys = compile.compile_target.define_keys();
let define_values = compile.compile_target.define_values();
Expand Down
76 changes: 35 additions & 41 deletions src/runtime/api/js_bundle_completion_task.rs
Original file line number Diff line number Diff line change
Expand Up @@ -287,8 +287,8 @@ impl JSBundleCompletionTask {
Ok(())
}

/// Port of `JSBundleCompletionTask.doCompilation`.
fn do_compilation(&mut self, output_files: &mut Vec<OutputFile>) -> CompileResult {
/// Port of `JSBundleCompletionTask.doCompilation`. Bundle thread: nothing in here may touch JS.
fn do_compilation(&self, output_files: &mut Vec<OutputFile>) -> CompileResult {
let compile_options = self
.config
.compile
Expand Down Expand Up @@ -426,10 +426,7 @@ impl JSBundleCompletionTask {
root_dir.fd,
module_prefix,
outfile_for_executable,
// SAFETY: `self.env` is the per-VM `DotEnv.Loader` stashed at
// construction; valid for the lifetime of the VirtualMachine, and
// nothing inside `to_executable` reaches it otherwise.
unsafe { &mut *self.env },
&compile_options.download,
self.config.format,
&WindowsOptions {
hide_console: compile_options.windows_hide_console,
Expand Down Expand Up @@ -559,6 +556,36 @@ impl JSBundleCompletionTask {
result
}

/// `Bun.build({ compile })`: the bundle becomes the executable before `on_complete` gets it.
fn compile_on_bundle_thread(&mut self) {
if self.config.compile.is_none()
|| self.cancelled.load(core::sync::atomic::Ordering::Acquire)
{
return;
}
let mut build = match core::mem::replace(&mut self.result, BundleV2Result::Pending) {
BundleV2Result::Value(build) => build,
not_bundled => {
self.result = not_bundled;
return;
}
};
let compile_result = self.do_compilation(&mut build.output_files);
// Nothing else flushes this thread's buffered stderr, which the above reports failures to.
bun_core::Output::flush();
self.result = match compile_result {
CompileResult::Success => BundleV2Result::Value(build),
CompileResult::Err(err) => {
self.log.add_error_fmt(
None,
bun_ast::Loc::EMPTY,
format_args!("{}", bstr::BStr::new(err.slice())),
);
BundleV2Result::Err(bun_bundler::Error::CompilationFailed)
}
};
}

pub(crate) fn on_complete_anytask(ctx: *mut Self) -> bun_event_loop::JsResult<()> {
crate::jsc_hooks::ActiveHandle::Bundle(NonNull::new(ctx).expect("completion")).unregister();
// For the +1 taken by `complete_on_bundle_thread` enqueue.
Expand Down Expand Up @@ -644,7 +671,7 @@ impl JSBundleCompletionTask {
}

// Copy the BackRef out (it is `Copy`) so `global_this` borrows a local
// instead of `*this` — `do_compilation`/`to_js_error` below need `&mut *this`.
// instead of `*this` — `to_js_error` below needs `&mut *this`.
let global_this_ref = this.global_this;
let global_this = global_this_ref.get();
// `Strong::swap` ties the returned `&mut JSPromise` to
Expand All @@ -654,40 +681,6 @@ impl JSBundleCompletionTask {
let promise: *mut JSPromise = this.promise.swap();
let promise = JSPromise::opaque_mut(promise);

// `do_compilation` borrows `&mut self` while needing
// `&mut output_files` from inside `self.result`. Temporarily move the
// Vec out via `take` so the method gets a disjoint `&mut self`.
if matches!(this.result, BundleV2Result::Value(_)) && this.config.compile.is_some() {
let mut output_files = match &mut this.result {
BundleV2Result::Value(build) => core::mem::take(&mut build.output_files),
// SAFETY: arm checked above.
_ => unsafe { core::hint::unreachable_unchecked() },
};
let compile_result = this.do_compilation(&mut output_files);
// `defer compile_result.deinit()` — `CompileResult` is a Rust enum
// with owned `Vec<u8>` payloads; drops at end of scope.

if let CompileResult::Err(err) = &compile_result {
// `bun.handleOom(log.addError(..., bun.handleOom(dupe(..))))`
this.log.add_error_fmt(
None,
bun_ast::Loc::EMPTY,
format_args!("{}", bstr::BStr::new(err.slice())),
);
// `this.result.value.deinit()` — owned fields drop with the
// overwrite below; `output_files` (moved out above) drops here.
drop(output_files);
this.result = BundleV2Result::Err(bun_bundler::Error::CompilationFailed);
} else {
// Put the compacted output_files back.
match &mut this.result {
BundleV2Result::Value(build) => build.output_files = output_files,
// SAFETY: arm checked above.
_ => unsafe { core::hint::unreachable_unchecked() },
}
}
}

// `to_js_error` borrows `&mut self`, which would overlap a
// `&mut this.result` match scrutinee. Dispatch the pending/err arms
// first, then take a fresh `&mut` for Value.
Expand Down Expand Up @@ -1115,6 +1108,7 @@ impl CompletionStruct for JSBundleCompletionTask {
}

fn complete_on_bundle_thread(&mut self) {
self.compile_on_bundle_thread();
// The bundle thread's last touch of this task and of the VM's memory:
// hand it back (always queued — the VM waits for it) and stop counting.
self.bundle_loop
Expand Down
2 changes: 1 addition & 1 deletion src/runtime/cli/build_command.rs
Original file line number Diff line number Diff line change
Expand Up @@ -889,7 +889,7 @@ impl BuildCommand {
&opt_public_path,
outfile,
// SAFETY: `env` is a process-lifetime singleton.
unsafe { &mut *env_ptr },
&compile_target.download_options(unsafe { &*env_ptr }),
opt_output_format,
&ctx.bundler_options.windows,
ctx.bundler_options
Expand Down
1 change: 0 additions & 1 deletion src/standalone_graph/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ bun_alloc.workspace = true
bun_core.workspace = true
bun_bundler.workspace = true
bun_collections.workspace = true
bun_dotenv.workspace = true
bun_exe_format.workspace = true
bun_http.workspace = true
bun_parsers.workspace = true
Expand Down
20 changes: 8 additions & 12 deletions src/standalone_graph/StandaloneModuleGraph.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1646,15 +1646,15 @@ pub(crate) fn inject(
}

use bun_core::Environment::OperatingSystem as CompileTargetOs;
pub use bun_options_types::compile_target::CompileTarget;
pub use bun_options_types::compile_target::{CompileTarget, DownloadOptions};

/// Moved up from `bun_options_types` (T3) so it can name
/// `bun_http::AsyncHTTP` directly
/// instead of routing through `extern "Rust"` shims; the only callers are the
/// two `download*` fns below in this crate.
pub(crate) fn download_to_path(
target: &CompileTarget,
env: &mut bun_dotenv::Loader,
download: &DownloadOptions,
dest_z: &ZStr,
Comment thread
claude[bot] marked this conversation as resolved.
) -> crate::Result<()> {
bun_http::http_thread::init(&Default::default());
Expand All @@ -1666,7 +1666,7 @@ pub(crate) fn download_to_path(
// TODO: This is way too much code necessary to send a single HTTP request...
let mut compressed_archive_bytes =
Box::new(bun_core::MutableString::init(24 * 1024 * 1024)?);
let mut url_buffer = [0u8; 2048];
let mut url_buffer = [0u8; CompileTarget::REGISTRY_URL_BUF_LEN];
let url_str = match target.to_npm_registry_url(&mut url_buffer) {
Ok(s) => s,
Err(err) => {
Expand All @@ -1681,10 +1681,6 @@ pub(crate) fn download_to_path(
// `progress.end()` below is sufficient: no fallible call sits between
// `refresher.start` and it, so every exit path (including the
// error returns after it) ends the node exactly once.
// Note: reshaped for borrowck — `get_http_proxy_for` borrows
// `env` for the proxy URL lifetime; read the bool first.
let reject_unauthorized = env.get_tls_reject_unauthorized();
let http_proxy: Option<bun_url::URL<'_>> = env.get_http_proxy_for(&url);
let progress = refresher.start(b"Downloading", 0);

let mut async_http = Box::new(bun_http::AsyncHTTP::init_sync(
Expand All @@ -1693,13 +1689,13 @@ pub(crate) fn download_to_path(
Default::default(),
b"",
b"",
http_proxy,
download.http_proxy.as_deref().map(bun_url::URL::parse),
None,
bun_http::FetchRedirect::Follow,
));
async_http.client.progress_node =
core::ptr::NonNull::new(core::ptr::from_mut(progress));
async_http.client.flags.reject_unauthorized = reject_unauthorized;
async_http.client.flags.reject_unauthorized = download.reject_unauthorized;
let send_result = async_http.send_sync(&mut compressed_archive_bytes);

progress.end();
Expand Down Expand Up @@ -1818,7 +1814,7 @@ pub fn to_executable(
root_dir: Fd,
module_prefix: &[u8],
outfile: &[u8],
env: &mut bun_dotenv::Loader,
download: &DownloadOptions,
output_format: Format,
windows_options: &WindowsOptions,
compile_exec_argv: &[u8],
Expand Down Expand Up @@ -1870,10 +1866,10 @@ pub fn to_executable(
let version_zstr = ZStr::from_slice_with_nul(&version_str[..]);

let mut needs_download: bool = true;
let dest_z = target.exe_path(&mut exe_path_buf, version_zstr, env, &mut needs_download);
let dest_z = target.exe_path(&mut exe_path_buf, version_zstr, &mut needs_download);

if needs_download {
if let Err(e) = download_to_path(target, env, dest_z) {
if let Err(e) = download_to_path(target, download, dest_z) {
return Ok(match e {
crate::Error::TargetNotFound => CompileResult::fail_fmt(format_args!(
"Target platform '{}' is not available for download. Check if this version of Bun supports this target.",
Expand Down
Loading
Loading