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 <jkelleyrtp@gmail.com>
This commit is contained in:
Evan AlmloffandJonathan Kelley authored and GitHub committed 2025-08-19 16:42:34 -07:00
1 parent a7805b8e2a
commit 165447a647
4 files changed
+56 -16

No files matched your search

+34 -2
View File
@@ -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. /// 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. /// Then return an `AssetManifest` containing all the assets found in the file.
pub(crate) fn extract_assets_from_file(path: impl AsRef<Path>) -> Result<AssetManifest> { pub(crate) async fn extract_assets_from_file(path: impl AsRef<Path>) -> Result<AssetManifest> {
let path = path.as_ref(); 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(); let mut file_contents = Vec::new();
file.read_to_end(&mut file_contents)?; file.read_to_end(&mut file_contents)?;
let mut reader = Cursor::new(&file_contents); let mut reader = Cursor::new(&file_contents);
@@ -369,3 +374,30 @@ pub(crate) fn extract_assets_from_file(path: impl AsRef<Path>) -> Result<AssetMa
Ok(manifest) Ok(manifest)
} }
/// Try to open a file for writing, retrying if the file is already open by another process.
///
/// This is useful on windows where antivirus software might grab the executable before we have a chance to read it.
async fn open_file_for_writing_with_timeout(
file: &Path,
options: &mut std::fs::OpenOptions,
) -> Result<std::fs::File> {
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());
}
}
}
}
}
+7 -5
View File
@@ -1317,12 +1317,14 @@ impl AppBuilder {
let name = id.to_string(); let name = id.to_string();
let some_entropy = name.split('-').next().unwrap(); 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 // Make a copy of the server exe with a new name
let entropy_server_exe = exe.with_file_name(format!( let entropy_server_exe = exe
"{}-{}", .with_file_name(format!("{}-{}", file_stem, some_entropy))
exe.file_name().unwrap().to_str().unwrap(), .with_extension(extension);
some_entropy
));
std::fs::copy(exe, &entropy_server_exe).unwrap(); std::fs::copy(exe, &entropy_server_exe).unwrap();
+14 -8
View File
@@ -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 time_end = SystemTime::now();
let mode = ctx.mode.clone(); let mode = ctx.mode.clone();
let bundle = self.bundle; 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 /// Collect the assets from the final executable and modify the binary in place to point to the right
/// hashed asset location. /// hashed asset location.
fn collect_assets(&self, exe: &Path, ctx: &BuildContext) -> Result<AssetManifest> { async fn collect_assets(&self, exe: &Path, ctx: &BuildContext) -> Result<AssetManifest> {
// walk every file in the incremental cache dir, reading and inserting items into the manifest. // walk every file in the incremental cache dir, reading and inserting items into the manifest.
let mut manifest = AssetManifest::default(); let mut manifest = AssetManifest::default();
// And then add from the exe directly, just in case it's LTO compiled and has no incremental cache // And then add from the exe directly, just in case it's LTO compiled and has no incremental cache
if !self.skip_assets { if !self.skip_assets {
ctx.status_extracting_assets(); ctx.status_extracting_assets();
manifest = super::assets::extract_assets_from_file(exe)?; manifest = super::assets::extract_assets_from_file(exe).await?;
} }
Ok(manifest) Ok(manifest)
@@ -1578,7 +1578,9 @@ impl BuildRequest {
} }
// Now extract the assets from the fat binary // 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 // If this is a web build, reset the index.html file in case it was modified by SSG
self.write_index_html(&artifacts.assets) self.write_index_html(&artifacts.assets)
@@ -2907,10 +2909,14 @@ impl BuildRequest {
fn platform_exe_name(&self) -> String { fn platform_exe_name(&self) -> String {
match self.bundle { match self.bundle {
BundleFormat::MacOS => self.executable_name().to_string(), // mac/ios are unixy and dont have an exe extension
BundleFormat::Ios => self.executable_name().to_string(), BundleFormat::MacOS | BundleFormat::Ios => self.executable_name().to_string(),
BundleFormat::Server => self.executable_name().to_string(),
BundleFormat::Windows => format!("{}.exe", self.executable_name()), // "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 // 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 // we include the user's rust code as a shared library with a fixed namespace
+1 -1
View File
@@ -16,7 +16,7 @@ pub struct BuildAssets {
impl BuildAssets { impl BuildAssets {
pub async fn run(self) -> Result<StructuredOutput> { pub async fn run(self) -> Result<StructuredOutput> {
let manifest = extract_assets_from_file(&self.executable)?; let manifest = extract_assets_from_file(&self.executable).await?;
create_dir_all(&self.destination)?; create_dir_all(&self.destination)?;
for asset in manifest.unique_assets() { for asset in manifest.unique_assets() {