Skip to content

Commit 0616480

Browse files
WayneWayne
authored andcommitted
feat: 完善Android报错
1 parent b4f4745 commit 0616480

7 files changed

Lines changed: 232 additions & 76 deletions

File tree

README.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,12 @@ Build a macOS `.app` bundle and `.dmg` with Simdock icons:
7373
./scripts/package-macos.sh
7474
```
7575

76+
Build a macOS `.pkg` installer:
77+
78+
```bash
79+
./scripts/package-macos-pkg.sh
80+
```
81+
7682
## Project Layout
7783

7884
```text
@@ -92,6 +98,8 @@ scripts/
9298
run-desktop.sh Desktop development runner.
9399
build-release.sh Release build helper.
94100
package-macos.sh macOS app bundle and DMG packaging helper.
101+
package-macos-pkg.sh
102+
macOS PKG installer packaging helper.
95103
size-report.sh Release binary size helper.
96104
```
97105

README.zh-CN.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,12 @@ Simdock是一个仅面向macOS的开源工具,用Rust构建,提供桌面应
7373
./scripts/package-macos.sh
7474
```
7575

76+
构建macOS`.pkg`安装包:
77+
78+
```bash
79+
./scripts/package-macos-pkg.sh
80+
```
81+
7682
## 项目结构
7783

7884
- `apps/simdock-cli/`:命令行应用。
@@ -87,6 +93,7 @@ Simdock是一个仅面向macOS的开源工具,用Rust构建,提供桌面应
8793
- `scripts/run-desktop.sh`:桌面端开发运行脚本。
8894
- `scripts/build-release.sh`:release构建脚本。
8995
- `scripts/package-macos.sh`:macOS app bundle和DMG打包脚本。
96+
- `scripts/package-macos-pkg.sh`:macOS PKG安装包打包脚本。
9097
- `scripts/size-report.sh`:release体积报告脚本。
9198

9299
## 文档

crates/simdock-core/src/provider/android.rs

Lines changed: 78 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
use std::{
2-
env, fs,
2+
env,
3+
ffi::OsString,
4+
fs,
35
path::{Path, PathBuf},
46
process::Stdio,
57
};
@@ -25,6 +27,8 @@ const ANDROID_CMDLINE_TOOLS_ARCHIVE: &str = "commandlinetools-mac-14742923_lates
2527
const MANAGED_JAVA_FEATURE_VERSION: u16 = 21;
2628
const SDKMANAGER_LICENSE_INPUT_REPEATS: usize = 200;
2729

30+
type ToolEnv = (&'static str, OsString);
31+
2832
#[derive(Debug, Clone)]
2933
/// Android Emulator平台Provider。
3034
///
@@ -745,24 +749,33 @@ fn parse_java_major_version(version: &str) -> Option<u16> {
745749
}
746750

747751
/// Android SDK工具运行时需要的一组环境变量。
748-
fn android_tool_envs<'a>(
749-
sdk_root: &'a Path,
750-
avd_root: &'a Path,
751-
java_runtime: &'a JavaRuntime,
752-
) -> Vec<(&'static str, &'a Path)> {
752+
fn android_tool_envs(sdk_root: &Path, avd_root: &Path, java_runtime: &JavaRuntime) -> Vec<ToolEnv> {
753753
let mut envs = vec![
754-
("ANDROID_SDK_ROOT", sdk_root),
755-
("ANDROID_HOME", sdk_root),
756-
("ANDROID_AVD_HOME", avd_root),
754+
("ANDROID_SDK_ROOT", sdk_root.as_os_str().to_os_string()),
755+
("ANDROID_HOME", sdk_root.as_os_str().to_os_string()),
756+
("ANDROID_AVD_HOME", avd_root.as_os_str().to_os_string()),
757757
];
758758

759759
if let Some(java_home) = java_runtime.managed_home() {
760-
envs.push(("JAVA_HOME", java_home));
760+
let java_binary = JavaRuntime::managed_java_binary(java_home);
761+
envs.push(("JAVA_HOME", java_home.as_os_str().to_os_string()));
762+
envs.push(("JAVACMD", java_binary.as_os_str().to_os_string()));
763+
envs.push(("PATH", prepend_to_path_env(&java_home.join("bin"))));
761764
}
762765

763766
envs
764767
}
765768

769+
/// 把托管JRE的`bin`放到PATH最前面,兜底兼容只查找`java`的SDK脚本。
770+
fn prepend_to_path_env(path: &Path) -> OsString {
771+
let mut paths = vec![path.to_path_buf()];
772+
if let Some(current_path) = env::var_os("PATH") {
773+
paths.extend(env::split_paths(&current_path));
774+
}
775+
776+
env::join_paths(paths).unwrap_or_else(|_| path.as_os_str().to_os_string())
777+
}
778+
766779
/// 确保Android SDK工具可用的Java运行时存在。
767780
///
768781
/// 优先复用系统Java;如果系统没有Java,就下载轻量JRE到Simdock
@@ -902,7 +915,11 @@ async fn ensure_android_java_runtime(
902915
let _ = fs::remove_dir_all(&extract_dir);
903916

904917
let Some((runtime, summary)) = probe_managed_java(java_root).await else {
905-
bail!("Managed Java runtime was extracted but java -version failed");
918+
let java_binary = JavaRuntime::managed_java_binary(java_root);
919+
bail!(
920+
"Managed Java runtime was extracted but {} could not run. Remove the managed Java runtime from Manage installed content and run one-click install again. If this is a company-managed Mac, ask IT to allow executing this binary.",
921+
java_binary.display()
922+
);
906923
};
907924
emit_log(
908925
sender,
@@ -1095,7 +1112,10 @@ async fn accept_android_licenses(
10951112
if probe.success {
10961113
Ok(())
10971114
} else {
1098-
bail!("Android SDK license acceptance failed: {}", probe.summary())
1115+
bail!(
1116+
"Android SDK license acceptance failed: {}",
1117+
android_tool_failure_summary("sdkmanager", &probe, Some(java_runtime))
1118+
)
10991119
}
11001120
}
11011121

@@ -1127,7 +1147,7 @@ async fn install_android_packages(
11271147
} else {
11281148
bail!(
11291149
"sdkmanager package installation failed: {}",
1130-
probe.summary()
1150+
android_tool_failure_summary("sdkmanager", &probe, Some(java_runtime))
11311151
)
11321152
}
11331153
}
@@ -1225,13 +1245,13 @@ async fn ensure_android_avd(
12251245

12261246
bail!(
12271247
"avdmanager failed to create Android virtual device: {}",
1228-
fallback_probe.summary()
1248+
android_tool_failure_summary("avdmanager", &fallback_probe, Some(java_runtime))
12291249
);
12301250
}
12311251

12321252
bail!(
12331253
"avdmanager failed to create Android virtual device: {}",
1234-
create_probe.summary()
1254+
android_tool_failure_summary("avdmanager", &create_probe, Some(java_runtime))
12351255
)
12361256
}
12371257

@@ -1253,7 +1273,7 @@ async fn tool_check(
12531273
name: &str,
12541274
path: Option<PathBuf>,
12551275
args: &[&str],
1256-
envs: &[(&str, &Path)],
1276+
envs: &[ToolEnv],
12571277
) -> DoctorCheck {
12581278
match path {
12591279
Some(path) => {
@@ -1268,7 +1288,7 @@ async fn tool_check(
12681288
format!(
12691289
"{name} probe failed at {}: {}",
12701290
path.display(),
1271-
probe.summary()
1291+
android_tool_failure_summary(name, &probe, None)
12721292
)
12731293
};
12741294

@@ -1286,6 +1306,41 @@ async fn tool_check(
12861306
}
12871307
}
12881308

1309+
/// 把Android工具里常见的Java定位失败转换成可执行的修复说明。
1310+
fn android_tool_failure_summary(
1311+
tool_name: &str,
1312+
probe: &CommandProbe,
1313+
java_runtime: Option<&JavaRuntime>,
1314+
) -> String {
1315+
let summary = probe.summary();
1316+
if !is_android_java_runtime_error(probe) {
1317+
return summary;
1318+
}
1319+
1320+
let runtime_hint = match java_runtime {
1321+
Some(JavaRuntime::Managed { java_home }) => format!(
1322+
"Simdock tried to use managed Java at {}. Remove the managed Java runtime from Manage installed content and run one-click install again. If this is a company-managed Mac, ask IT to allow executing {}.",
1323+
java_home.display(),
1324+
JavaRuntime::managed_java_binary(java_home).display()
1325+
),
1326+
_ => "Simdock could not use a Java runtime for Android SDK tools. Run one-click install again so Simdock can provision managed Java. If this is a company-managed Mac, ask IT to allow Simdock's managed Java runtime under ~/Library/Application Support/com.simdock.Simdock/java-runtime.".to_string(),
1327+
};
1328+
1329+
format!(
1330+
"{tool_name} could not locate or execute Java. {runtime_hint} Original output: {summary}"
1331+
)
1332+
}
1333+
1334+
/// 识别macOS和Android命令行工具输出中的Java缺失/不可执行错误。
1335+
fn is_android_java_runtime_error(probe: &CommandProbe) -> bool {
1336+
let output = format!("{}\n{}", probe.stdout, probe.stderr).to_lowercase();
1337+
output.contains("unable to locate a java runtime")
1338+
|| output.contains("no java runtime present")
1339+
|| output.contains("could not find java")
1340+
|| output.contains("could not find a valid java")
1341+
|| output.contains("java_home is not defined correctly")
1342+
}
1343+
12891344
/// 运行PATH中的命令并收集输出。
12901345
async fn run_command(program: &str, args: &[&str]) -> CommandProbe {
12911346
finish_probe({
@@ -1310,13 +1365,13 @@ async fn run_path_command(program: &Path, args: &[&str]) -> CommandProbe {
13101365
async fn run_path_command_with_env(
13111366
program: &Path,
13121367
args: &[&str],
1313-
envs: &[(&str, &Path)],
1368+
envs: &[ToolEnv],
13141369
) -> CommandProbe {
13151370
finish_probe({
13161371
let mut command = Command::new(program);
13171372
command.args(args);
13181373
for (key, value) in envs {
1319-
command.env(key, value);
1374+
command.env(*key, value);
13201375
}
13211376
command
13221377
})
@@ -1327,15 +1382,15 @@ async fn run_path_command_with_env(
13271382
async fn run_command_streamed(
13281383
program: &str,
13291384
args: &[String],
1330-
envs: &[(&str, &Path)],
1385+
envs: &[ToolEnv],
13311386
task_id: &str,
13321387
sender: Option<&TaskSender>,
13331388
) -> CommandProbe {
13341389
let mut command = Command::new(program);
13351390
command.args(args);
13361391

13371392
for (key, value) in envs {
1338-
command.env(key, value);
1393+
command.env(*key, value);
13391394
}
13401395

13411396
finish_probe_streamed(command, program, args, None, task_id, sender).await
@@ -1345,7 +1400,7 @@ async fn run_command_streamed(
13451400
async fn run_path_command_streamed_with_input(
13461401
program: &Path,
13471402
args: &[String],
1348-
envs: &[(&str, &Path)],
1403+
envs: &[ToolEnv],
13491404
input: Option<String>,
13501405
task_id: &str,
13511406
sender: Option<&TaskSender>,
@@ -1354,7 +1409,7 @@ async fn run_path_command_streamed_with_input(
13541409
command.args(args);
13551410

13561411
for (key, value) in envs {
1357-
command.env(key, value);
1412+
command.env(*key, value);
13581413
}
13591414

13601415
let program_label = program.display().to_string();

docs/packaging.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,25 @@ The macOS packaging script:
2828
- Adds a Finder custom icon to the generated `.dmg` file when macOS icon tools are available.
2929
- Adds an `/Applications` symlink for drag-and-drop installation.
3030

31+
## macOS PKG
32+
33+
```bash
34+
./scripts/package-macos-pkg.sh
35+
```
36+
37+
The PKG packaging script:
38+
39+
- Reads the app version from the root `Cargo.toml`.
40+
- Builds `target/macos/Simdock.app`.
41+
- Creates `target/macos/Simdock-<version>.pkg`.
42+
- Installs `Simdock.app` into `/Applications`.
43+
44+
Create a signed installer package by passing a Developer ID Installer identity:
45+
46+
```bash
47+
PKG_SIGN_IDENTITY="Developer ID Installer: Your Name (TEAMID)" ./scripts/package-macos-pkg.sh
48+
```
49+
3150
## Size Report
3251

3352
```bash

scripts/build-macos-app.sh

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
#!/usr/bin/env bash
2+
set -euo pipefail
3+
4+
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
5+
APP_NAME="Simdock"
6+
BINARY_NAME="simdock-desktop"
7+
BUNDLE_ID="com.simdock.Simdock"
8+
VERSION="$(awk -F '"' '/^version = / { print $2; exit }' "$ROOT_DIR/Cargo.toml")"
9+
10+
OUT_DIR="$ROOT_DIR/target/macos"
11+
APP_DIR="$OUT_DIR/$APP_NAME.app"
12+
ICON_SOURCE="$ROOT_DIR/assets/brand/simdock.icns"
13+
14+
if [[ -z "$VERSION" ]]; then
15+
echo "Could not read version from Cargo.toml" >&2
16+
exit 1
17+
fi
18+
19+
if [[ ! -f "$ICON_SOURCE" ]]; then
20+
echo "Missing macOS icon: $ICON_SOURCE" >&2
21+
exit 1
22+
fi
23+
24+
echo "Building release binary..."
25+
cargo build --release -p "$BINARY_NAME"
26+
27+
echo "Creating app bundle..."
28+
rm -rf "$APP_DIR"
29+
mkdir -p "$APP_DIR/Contents/MacOS" "$APP_DIR/Contents/Resources"
30+
31+
cp "$ROOT_DIR/target/release/$BINARY_NAME" "$APP_DIR/Contents/MacOS/$APP_NAME"
32+
chmod +x "$APP_DIR/Contents/MacOS/$APP_NAME"
33+
cp "$ICON_SOURCE" "$APP_DIR/Contents/Resources/$APP_NAME.icns"
34+
printf 'APPL????' > "$APP_DIR/Contents/PkgInfo"
35+
36+
cat > "$APP_DIR/Contents/Info.plist" <<PLIST
37+
<?xml version="1.0" encoding="UTF-8"?>
38+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
39+
<plist version="1.0">
40+
<dict>
41+
<key>CFBundleDevelopmentRegion</key>
42+
<string>en</string>
43+
<key>CFBundleDisplayName</key>
44+
<string>$APP_NAME</string>
45+
<key>CFBundleExecutable</key>
46+
<string>$APP_NAME</string>
47+
<key>CFBundleIconFile</key>
48+
<string>$APP_NAME</string>
49+
<key>CFBundleIdentifier</key>
50+
<string>$BUNDLE_ID</string>
51+
<key>CFBundleName</key>
52+
<string>$APP_NAME</string>
53+
<key>CFBundlePackageType</key>
54+
<string>APPL</string>
55+
<key>CFBundleShortVersionString</key>
56+
<string>$VERSION</string>
57+
<key>CFBundleVersion</key>
58+
<string>$VERSION</string>
59+
<key>LSApplicationCategoryType</key>
60+
<string>public.app-category.developer-tools</string>
61+
<key>LSMinimumSystemVersion</key>
62+
<string>13.0</string>
63+
<key>NSHighResolutionCapable</key>
64+
<true/>
65+
</dict>
66+
</plist>
67+
PLIST
68+
69+
if command -v codesign >/dev/null 2>&1; then
70+
echo "Applying ad-hoc code signature..."
71+
codesign --force --deep --sign - "$APP_DIR" >/dev/null
72+
fi
73+
74+
echo "Built app: $APP_DIR"

0 commit comments

Comments
 (0)