Skip to content

Commit d4ffd09

Browse files
committed
Allow installing sdk from extracted .app
1 parent e069e16 commit d4ffd09

File tree

2 files changed

+128
-64
lines changed

2 files changed

+128
-64
lines changed

src-tauri/src/builder/sdk.rs

Lines changed: 92 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ use regex::Regex;
33
use serde::{Deserialize, Serialize};
44
use std::collections::{HashMap, HashSet};
55
use std::fs;
6+
use std::io::ErrorKind;
67
use std::path::{Component, Path, PathBuf};
78
use std::process::Command;
89
use tauri::{AppHandle, Manager, Window};
@@ -28,13 +29,22 @@ pub async fn install_sdk_operation(
2829
window: Window,
2930
xcode_path: String,
3031
toolchain_path: String,
32+
is_dir: bool,
3133
) -> Result<(), String> {
3234
let op = Operation::new("install_sdk".to_string(), &window);
3335
op.start("create_stage")?;
3436
let work_dir = op
3537
.fail_if_err("create_stage", linux_temp_dir())?
3638
.join("DarwinSDKBuild");
37-
let res = install_sdk_internal(app, xcode_path, toolchain_path, work_dir.clone(), &op).await;
39+
let res = install_sdk_internal(
40+
app,
41+
xcode_path,
42+
toolchain_path,
43+
work_dir.clone(),
44+
is_dir,
45+
&op,
46+
)
47+
.await;
3848
op.start("cleanup")?;
3949
let cleanup_result = if work_dir.exists() {
4050
remove_dir_all(&work_dir)
@@ -71,9 +81,10 @@ async fn install_sdk_internal(
7181
xcode_path: String,
7282
toolchain_path: String,
7383
work_dir: PathBuf,
84+
is_dir: bool,
7485
op: &Operation<'_>,
7586
) -> Result<(), String> {
76-
if xcode_path.is_empty() || !xcode_path.ends_with(".xip") {
87+
if xcode_path.is_empty() || (!xcode_path.ends_with(".xip") && !is_dir) {
7788
return op.fail("create_stage", "Xcode not found".to_string());
7889
}
7990
if toolchain_path.is_empty() {
@@ -114,7 +125,7 @@ async fn install_sdk_internal(
114125
op.move_on("create_stage", "install_toolset")?;
115126
op.fail_if_err("install_toolset", install_toolset(&output_dir).await)?;
116127
op.complete("install_toolset")?;
117-
let dev = install_developer(&app, &output_dir, &xcode_path, op).await?;
128+
let dev = install_developer(&app, &output_dir, &xcode_path, is_dir, op).await?;
118129
op.start("write_metadata")?;
119130

120131
let iphone_os_sdk = sdk(&dev, "iPhoneOS")?;
@@ -307,72 +318,78 @@ async fn install_developer(
307318
app: &AppHandle,
308319
output_path: &PathBuf,
309320
xcode_path: &str,
321+
is_dir: bool,
310322
op: &Operation<'_>,
311323
) -> Result<PathBuf, String> {
312324
op.start("extract_xip")?;
313-
let dev_stage = output_path.join("DeveloperStage");
314-
op.fail_if_err_map("extract_xip", fs::create_dir_all(&dev_stage), |e| {
315-
format!("Failed to create DeveloperStage directory: {}", e)
316-
})?;
317325

318-
let unxip_path = op.fail_if_err_map(
319-
"extract_xip",
320-
app.path()
321-
.resolve("unxip", tauri::path::BaseDirectory::Resource),
322-
|e| format!("Failed to resolve unxip path: {}", e),
323-
)?;
326+
let dev_stage = output_path.join("DeveloperStage");
327+
let mut app_path = PathBuf::from(xcode_path);
328+
if !is_dir {
329+
op.fail_if_err_map("extract_xip", fs::create_dir_all(&dev_stage), |e| {
330+
format!("Failed to create DeveloperStage directory: {}", e)
331+
})?;
324332

325-
#[cfg(target_os = "windows")]
326-
let status = Command::new("wsl")
327-
.arg("bash")
328-
.arg("-c")
329-
.arg(format!(
330-
"{} {} {}",
331-
windows_to_wsl_path(&unxip_path.to_string_lossy())?,
332-
windows_to_wsl_path(&xcode_path)?,
333-
windows_to_wsl_path(&dev_stage.to_string_lossy())?
334-
))
335-
.creation_flags(CREATE_NO_WINDOW)
336-
.output();
337-
#[cfg(not(target_os = "windows"))]
338-
let status = Command::new(unxip_path)
339-
.current_dir(&dev_stage)
340-
.arg(xcode_path)
341-
.output();
342-
if let Err(e) = status {
343-
return op.fail("extract_xip", format!("Failed to run unxip: {}", e));
344-
}
345-
let status = status.unwrap();
346-
if !status.status.success() {
347-
return op.fail(
333+
let unxip_path = op.fail_if_err_map(
348334
"extract_xip",
349-
format!(
350-
"{}\nProcess exited with code {}",
351-
String::from_utf8_lossy(&status.stderr.trim_ascii()),
352-
status.status.code().unwrap_or(0)
353-
),
354-
);
355-
}
335+
app.path()
336+
.resolve("unxip", tauri::path::BaseDirectory::Resource),
337+
|e| format!("Failed to resolve unxip path: {}", e),
338+
)?;
339+
340+
#[cfg(target_os = "windows")]
341+
let status = Command::new("wsl")
342+
.arg("bash")
343+
.arg("-c")
344+
.arg(format!(
345+
"{} {} {}",
346+
windows_to_wsl_path(&unxip_path.to_string_lossy())?,
347+
windows_to_wsl_path(&xcode_path)?,
348+
windows_to_wsl_path(&dev_stage.to_string_lossy())?
349+
))
350+
.creation_flags(CREATE_NO_WINDOW)
351+
.output();
352+
#[cfg(not(target_os = "windows"))]
353+
let status = Command::new(unxip_path)
354+
.current_dir(&dev_stage)
355+
.arg(xcode_path)
356+
.output();
357+
if let Err(e) = status {
358+
return op.fail("extract_xip", format!("Failed to run unxip: {}", e));
359+
}
360+
let status = status.unwrap();
361+
if !status.status.success() {
362+
return op.fail(
363+
"extract_xip",
364+
format!(
365+
"{}\nProcess exited with code {}",
366+
String::from_utf8_lossy(&status.stderr.trim_ascii()),
367+
status.status.code().unwrap_or(0)
368+
),
369+
);
370+
}
356371

357-
let app_dirs = op
358-
.fail_if_err_map("extract_xip", fs::read_dir(&dev_stage), |e| {
359-
format!("Failed to read DeveloperStage directory: {}", e)
360-
})?
361-
.filter_map(Result::ok)
362-
.filter(|entry| entry.path().extension().map_or(false, |ext| ext == "app"))
363-
.collect::<Vec<_>>();
364-
if app_dirs.len() != 1 {
365-
return op.fail(
366-
"extract_xip",
367-
format!(
368-
"Expected one .app in DeveloperStage, found {}",
369-
app_dirs.len()
370-
),
371-
);
372+
let app_dirs = op
373+
.fail_if_err_map("extract_xip", fs::read_dir(&dev_stage), |e| {
374+
format!("Failed to read DeveloperStage directory: {}", e)
375+
})?
376+
.filter_map(Result::ok)
377+
.filter(|entry| entry.path().extension().map_or(false, |ext| ext == "app"))
378+
.collect::<Vec<_>>();
379+
if app_dirs.len() != 1 {
380+
return op.fail(
381+
"extract_xip",
382+
format!(
383+
"Expected one .app in DeveloperStage, found {}",
384+
app_dirs.len()
385+
),
386+
);
387+
}
388+
389+
app_path = app_dirs[0].path();
372390
}
373391

374392
op.move_on("extract_xip", "copy_files")?;
375-
let app_path = app_dirs[0].path();
376393
let dev = output_path.join("Developer");
377394
op.fail_if_err_map("copy_files", fs::create_dir_all(&dev), |e| {
378395
format!("Failed to create Developer directory: {}", e)
@@ -390,9 +407,11 @@ async fn install_developer(
390407
"copy_files",
391408
copy_developer(&contents_developer, &dev, Path::new("Contents/Developer")),
392409
)?;
393-
op.fail_if_err_map("copy_files", remove_dir_all(&dev_stage), |e| {
394-
format!("Failed to remove DeveloperStage directory: {}", e)
395-
})?;
410+
if dev_stage.exists() {
411+
op.fail_if_err_map("copy_files", remove_dir_all(&dev_stage), |e| {
412+
format!("Failed to remove DeveloperStage directory: {}", e)
413+
})?;
414+
}
396415

397416
for platform in ["iPhoneOS", "MacOSX", "iPhoneSimulator"] {
398417
let lib = "../../../../../Library";
@@ -485,7 +504,17 @@ fn copy_developer(src: &Path, dst: &Path, rel: &Path) -> Result<(), String> {
485504
fs::create_dir_all(parent)
486505
.map_err(|e| format!("Failed to create parent dir: {}", e))?;
487506
}
488-
fs::rename(&src_path, &dst_path).map_err(|e| format!("Failed to copy file: {}", e))?;
507+
match fs::rename(&src_path, &dst_path) {
508+
Ok(_) => {}
509+
Err(e) => {
510+
if e.kind() == ErrorKind::CrossesDevices {
511+
fs::copy(&src_path, &dst_path)
512+
.map_err(|e2| format!("Failed to copy file across devices: {}", e2))?;
513+
} else {
514+
return Err(format!("Failed to move file: {}", e));
515+
}
516+
}
517+
}
489518
}
490519
}
491520
Ok(())

src/components/SDKMenu.tsx

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,31 @@ export default () => {
3939
const params = {
4040
xcodePath: xipPath,
4141
toolchainPath: selectedToolchain?.path || "",
42+
isDir: false,
43+
};
44+
await startOperation(installSdkOperation, params);
45+
checkSDK();
46+
}, [selectedToolchain, addToast]);
47+
48+
const installFromFolder = useCallback(async () => {
49+
let xcodePath = await open({
50+
directory: true,
51+
multiple: false,
52+
filters: [
53+
{
54+
name: "XCode.app",
55+
extensions: ["app"],
56+
},
57+
],
58+
});
59+
if (!xcodePath) {
60+
addToast.error("No Xcode selected");
61+
return;
62+
}
63+
const params = {
64+
xcodePath,
65+
toolchainPath: selectedToolchain?.path || "",
66+
isDir: true,
4267
};
4368
await startOperation(installSdkOperation, params);
4469
checkSDK();
@@ -104,7 +129,17 @@ export default () => {
104129
>
105130
Download XCode 16.3
106131
</Button>
107-
<Button variant="soft" onClick={install} disabled={!selectedToolchain}>
132+
<Button
133+
variant="soft"
134+
onClick={(e) => {
135+
if (e.shiftKey) {
136+
installFromFolder();
137+
} else {
138+
install();
139+
}
140+
}}
141+
disabled={!selectedToolchain}
142+
>
108143
{hasDarwinSDK ? "Reinstall SDK" : "Install SDK"}
109144
</Button>
110145
<Button variant="soft" onClick={checkSDK} disabled={!selectedToolchain}>

0 commit comments

Comments
 (0)