From 165447a6479ef6ca02ca2fea192dce7a3f0c8979 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Tue, 19 Aug 2025 18:42:34 -0500 Subject: [PATCH] Fix the server extension on windows server bundles (#4555) * fix the server extension on windows server bundles * fix formatting * retry extracting assets if windows antivirus is holding the lock * fix exe hash with extension * add a timeout * wip, make async * use async methods --------- Co-authored-by: Jonathan Kelley --- packages/cli/src/build/assets.rs | 36 ++++++++++++++++++++++++++-- packages/cli/src/build/builder.rs | 12 ++++++---- packages/cli/src/build/request.rs | 22 ++++++++++------- packages/cli/src/cli/build_assets.rs | 2 +- 4 files changed, 56 insertions(+), 16 deletions(-) diff --git a/packages/cli/src/build/assets.rs b/packages/cli/src/build/assets.rs index 4e37b19c9..e67947f4c 100644 --- a/packages/cli/src/build/assets.rs +++ b/packages/cli/src/build/assets.rs @@ -293,9 +293,14 @@ fn find_wasm_symbol_offsets<'a, R: ReadRef<'a>>( /// Find all assets in the given file, hash them, and write them back to the file. /// Then return an `AssetManifest` containing all the assets found in the file. -pub(crate) fn extract_assets_from_file(path: impl AsRef) -> Result { +pub(crate) async fn extract_assets_from_file(path: impl AsRef) -> Result { let path = path.as_ref(); - let mut file = std::fs::File::options().write(true).read(true).open(path)?; + let mut file = open_file_for_writing_with_timeout( + path, + std::fs::OpenOptions::new().write(true).read(true), + ) + .await?; + let mut file_contents = Vec::new(); file.read_to_end(&mut file_contents)?; let mut reader = Cursor::new(&file_contents); @@ -369,3 +374,30 @@ pub(crate) fn extract_assets_from_file(path: impl AsRef) -> Result Result { + let start_time = std::time::Instant::now(); + let timeout = std::time::Duration::from_secs(5); + loop { + match options.open(file) { + Ok(file) => return Ok(file), + Err(e) => { + if cfg!(windows) && e.raw_os_error() == Some(32) && start_time.elapsed() < timeout { + // File is already open, wait and retry + tracing::trace!( + "Failed to open file because another process is using it. Retrying..." + ); + tokio::time::sleep(std::time::Duration::from_millis(50)).await; + } else { + return Err(e.into()); + } + } + } + } +} diff --git a/packages/cli/src/build/builder.rs b/packages/cli/src/build/builder.rs index d27051940..10ca3c09a 100644 --- a/packages/cli/src/build/builder.rs +++ b/packages/cli/src/build/builder.rs @@ -1317,12 +1317,14 @@ impl AppBuilder { let name = id.to_string(); let some_entropy = name.split('-').next().unwrap(); + // Split up the exe into the file stem and extension + let extension = exe.extension().unwrap_or_default(); + let file_stem = exe.file_stem().unwrap().to_str().unwrap(); + // Make a copy of the server exe with a new name - let entropy_server_exe = exe.with_file_name(format!( - "{}-{}", - exe.file_name().unwrap().to_str().unwrap(), - some_entropy - )); + let entropy_server_exe = exe + .with_file_name(format!("{}-{}", file_stem, some_entropy)) + .with_extension(extension); std::fs::copy(exe, &entropy_server_exe).unwrap(); diff --git a/packages/cli/src/build/request.rs b/packages/cli/src/build/request.rs index a6eb49228..12f35ffd0 100644 --- a/packages/cli/src/build/request.rs +++ b/packages/cli/src/build/request.rs @@ -1073,7 +1073,7 @@ impl BuildRequest { ); } - let assets = self.collect_assets(&exe, ctx)?; + let assets = self.collect_assets(&exe, ctx).await?; let time_end = SystemTime::now(); let mode = ctx.mode.clone(); let bundle = self.bundle; @@ -1100,14 +1100,14 @@ impl BuildRequest { /// Collect the assets from the final executable and modify the binary in place to point to the right /// hashed asset location. - fn collect_assets(&self, exe: &Path, ctx: &BuildContext) -> Result { + async fn collect_assets(&self, exe: &Path, ctx: &BuildContext) -> Result { // walk every file in the incremental cache dir, reading and inserting items into the manifest. let mut manifest = AssetManifest::default(); // And then add from the exe directly, just in case it's LTO compiled and has no incremental cache if !self.skip_assets { ctx.status_extracting_assets(); - manifest = super::assets::extract_assets_from_file(exe)?; + manifest = super::assets::extract_assets_from_file(exe).await?; } Ok(manifest) @@ -1578,7 +1578,9 @@ impl BuildRequest { } // Now extract the assets from the fat binary - artifacts.assets = self.collect_assets(&self.patch_exe(artifacts.time_start), ctx)?; + artifacts.assets = self + .collect_assets(&self.patch_exe(artifacts.time_start), ctx) + .await?; // If this is a web build, reset the index.html file in case it was modified by SSG self.write_index_html(&artifacts.assets) @@ -2907,10 +2909,14 @@ impl BuildRequest { fn platform_exe_name(&self) -> String { match self.bundle { - BundleFormat::MacOS => self.executable_name().to_string(), - BundleFormat::Ios => self.executable_name().to_string(), - BundleFormat::Server => self.executable_name().to_string(), - BundleFormat::Windows => format!("{}.exe", self.executable_name()), + // mac/ios are unixy and dont have an exe extension + BundleFormat::MacOS | BundleFormat::Ios => self.executable_name().to_string(), + + // "server" and windows can be the same + BundleFormat::Server | BundleFormat::Windows => match self.triple.operating_system { + OperatingSystem::Windows => format!("{}.exe", self.executable_name()), + _ => self.executable_name().to_string(), + }, // from the apk spec, the root exe is a shared library // we include the user's rust code as a shared library with a fixed namespace diff --git a/packages/cli/src/cli/build_assets.rs b/packages/cli/src/cli/build_assets.rs index 4023d58f4..3ccaa2a80 100644 --- a/packages/cli/src/cli/build_assets.rs +++ b/packages/cli/src/cli/build_assets.rs @@ -16,7 +16,7 @@ pub struct BuildAssets { impl BuildAssets { pub async fn run(self) -> Result { - let manifest = extract_assets_from_file(&self.executable)?; + let manifest = extract_assets_from_file(&self.executable).await?; create_dir_all(&self.destination)?; for asset in manifest.unique_assets() {