Compare commits
4
Commits
38832df095
...
v0.1.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0bfe22aa6f | ||
|
|
a6013c7d22 | ||
|
|
37ccaaf23f | ||
|
|
dc4fac8391 |
No files matched your search
@@ -83,9 +83,48 @@ jobs:
|
|||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
# +crt-static 是否真的生效,只看导入表:出现 VCRUNTIME/MSVCP/msvcr/ucrtbase 说明还得装
|
# +crt-static 是否真的生效,只看导入表:出现 VCRUNTIME/MSVCP/msvcr/ucrtbase 说明还得装
|
||||||
# 可再发行包,那就不是"单文件"了,直接 fail。
|
# 可再发行包,那就不是"单文件"了,直接 fail。
|
||||||
DUMPBIN=$(command -v dumpbin || true)
|
#
|
||||||
|
# dumpbin 定位链(三级,全不命中才 fail —— 门禁不降级,只是把"猜路径"换成官方定位):
|
||||||
|
# ① vswhere:VS 官方定位器,安装器自身路径固定,与 VS 版本/版次/安装布局无关;
|
||||||
|
# ② find 兜底:Program Files 与 Program Files (x86) 两个 VS 根都扫(Build Tools 落在 x86 根);
|
||||||
|
# ③ PATH:镜像若已把工具链预置进 PATH 则直接可用。
|
||||||
|
# PATH 排最后是刻意的:PATH 最不可控,官方定位器优先。
|
||||||
|
VSWHERE="C:/Program Files (x86)/Microsoft Visual Studio/Installer/vswhere.exe"
|
||||||
|
DUMPBIN=""
|
||||||
|
VIA=""
|
||||||
|
if [ -x "$VSWHERE" ]; then
|
||||||
|
# -find 的模式优先用反斜杠(vswhere 官方文档即此形式),再试正斜杠以规避分隔符差异。
|
||||||
|
for pattern in 'VC\Tools\MSVC\*\bin\Hostx64\x64\dumpbin.exe' \
|
||||||
|
'VC/Tools/MSVC/*/bin/Hostx64/x64/dumpbin.exe'; do
|
||||||
|
hit=$("$VSWHERE" -latest -products '*' -find "$pattern" 2>/dev/null | head -n1 || true)
|
||||||
|
if [ -n "$hit" ]; then
|
||||||
|
DUMPBIN=${hit//\\//}
|
||||||
|
VIA="vswhere"
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
fi
|
||||||
if [ -z "$DUMPBIN" ]; then
|
if [ -z "$DUMPBIN" ]; then
|
||||||
DUMPBIN=$(ls -d "C:/Program Files/Microsoft Visual Studio/"*/VC/Tools/MSVC/*/bin/Hostx64/x64/dumpbin.exe 2>/dev/null | head -n1 || true)
|
# 镜像里的 VS 根形如 <年>/<版次>(2022/Enterprise、2022/BuildTools……),
|
||||||
|
# 固定层数的 glob 会在 <年>/<版次> 这两级踩空,所以按路径模式扫。
|
||||||
|
for root in "C:/Program Files/Microsoft Visual Studio" \
|
||||||
|
"C:/Program Files (x86)/Microsoft Visual Studio"; do
|
||||||
|
[ -d "$root" ] || continue
|
||||||
|
hit=$(find "$root" -maxdepth 11 -type f \
|
||||||
|
-path '*/VC/Tools/MSVC/*/bin/Hostx64/x64/dumpbin.exe' 2>/dev/null | head -n1 || true)
|
||||||
|
if [ -n "$hit" ]; then
|
||||||
|
DUMPBIN=$hit
|
||||||
|
VIA="find: $root"
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
if [ -z "$DUMPBIN" ]; then
|
||||||
|
hit=$(command -v dumpbin || true)
|
||||||
|
if [ -n "$hit" ]; then
|
||||||
|
DUMPBIN=$hit
|
||||||
|
VIA="PATH"
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
# 工具缺失同样 fail:windows-latest 自带 VS 工具链,基本找得到;
|
# 工具缺失同样 fail:windows-latest 自带 VS 工具链,基本找得到;
|
||||||
# "找不到就跳过"等于让这道门禁在最需要它的时候静默失效。
|
# "找不到就跳过"等于让这道门禁在最需要它的时候静默失效。
|
||||||
@@ -93,6 +132,7 @@ jobs:
|
|||||||
echo "::error::找不到 dumpbin,无法校验导入表(静态 CRT 门禁不降级)"
|
echo "::error::找不到 dumpbin,无法校验导入表(静态 CRT 门禁不降级)"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
echo "dumpbin: $DUMPBIN(定位自 $VIA)"
|
||||||
"$DUMPBIN" -nologo -dependents target/release/unlucky-launcher.exe | tee dependents.txt
|
"$DUMPBIN" -nologo -dependents target/release/unlucky-launcher.exe | tee dependents.txt
|
||||||
if grep -Eiq 'VCRUNTIME|MSVCP|MSVCR|ucrtbase\.dll' dependents.txt; then
|
if grep -Eiq 'VCRUNTIME|MSVCP|MSVCR|ucrtbase\.dll' dependents.txt; then
|
||||||
echo "::error::静态 CRT 未生效:exe 仍动态依赖 VC++ 可再发行包"
|
echo "::error::静态 CRT 未生效:exe 仍动态依赖 VC++ 可再发行包"
|
||||||
|
|||||||
@@ -186,13 +186,24 @@ pub fn dedupe(runtimes: Vec<JavaRuntime>) -> Vec<JavaRuntime> {
|
|||||||
kept
|
kept
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 去重键:去掉 Windows 的 `\\?\` 前缀、统一分隔符、大小写不敏感(Windows)。
|
/// 去重键:剥掉 Windows 的 `\\?\` 前缀、分隔符一律折成 `/`、大小写只在 Windows 折叠。
|
||||||
|
///
|
||||||
|
/// 前两步与平台无关:同一条 `bin/java[.exe]` 用 `/` 还是 `\` 拼出来都是同一个文件,
|
||||||
|
/// 键必须相同——`JAVA_HOME` 的写法与扫盘拼出来的路径常在这点上不一致,键不齐就重复列。
|
||||||
|
/// 大小写折叠**只能**跟着平台走:NTFS 不区分大小写,而 POSIX 上 `Java` 与 `java` 是两个
|
||||||
|
/// 目录,折了会把两份真实安装误并成一个。
|
||||||
|
///
|
||||||
|
/// 代价(已知且有意的取舍):POSIX 上文件名里的字面反斜杠也会被当分隔符。JDK 安装路径带
|
||||||
|
/// 反斜杠属于病态情况,换来的是「同一条 java 的两种拼法永远同键」这条平台无关的不变量。
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn path_key(path: &Path) -> String {
|
pub fn path_key(path: &Path) -> String {
|
||||||
let text = path.display().to_string();
|
let text = path.display().to_string();
|
||||||
let text = text.strip_prefix(r"\\?\").unwrap_or(&text).to_string();
|
let text = text
|
||||||
|
.strip_prefix(r"\\?\")
|
||||||
|
.unwrap_or(&text)
|
||||||
|
.replace('\\', "/");
|
||||||
if cfg!(windows) {
|
if cfg!(windows) {
|
||||||
text.replace('/', "\\").to_ascii_lowercase()
|
text.to_ascii_lowercase()
|
||||||
} else {
|
} else {
|
||||||
text
|
text
|
||||||
}
|
}
|
||||||
@@ -236,11 +247,56 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn path_key_normalises_separators() {
|
fn path_key_normalises_separators_on_every_platform() {
|
||||||
let left = path_key(std::path::Path::new("C:/a/b/java.exe"));
|
// 平台无关的不变量:同一条 `bin/java` 用 `/` 还是 `\` 拼出来,键必须相同。
|
||||||
let right = path_key(std::path::Path::new("C:\\a\\b\\java.exe"));
|
// (M49:原来这里只在 Windows 上成立,Linux 上 `\` 原样留着 → 断言挂。)
|
||||||
assert_eq!(left, right);
|
let forward = path_key(std::path::Path::new("C:/a/b/java.exe"));
|
||||||
assert!(!path_key(std::path::Path::new(r"\\?\C:\a\java.exe")).starts_with(r"\\"));
|
let backward = path_key(std::path::Path::new("C:\\a\\b\\java.exe"));
|
||||||
|
assert_eq!(forward, backward);
|
||||||
|
assert!(
|
||||||
|
!forward.contains('\\'),
|
||||||
|
"键里不该残留反斜杠(与平台无关):{forward}"
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
!forward.starts_with(r"\\"),
|
||||||
|
"`\\\\?\\` 前缀不进键:{forward}"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn one_install_normalises_to_one_key_whatever_the_spelling() {
|
||||||
|
// 回归钉子:`JAVA_HOME` 与扫盘常对同一个 java 给出不同写法,四种拼法必须同键。
|
||||||
|
let spellings = [
|
||||||
|
"C:/Program Files/Java/jdk-21/bin/java.exe",
|
||||||
|
r"C:\Program Files\Java\jdk-21\bin\java.exe",
|
||||||
|
r"\\?\C:\Program Files\Java\jdk-21\bin\java.exe",
|
||||||
|
r"C:\Program Files\Java\jdk-21/bin/java.exe",
|
||||||
|
];
|
||||||
|
let key = path_key(std::path::Path::new(spellings[0]));
|
||||||
|
for spelling in spellings {
|
||||||
|
assert_eq!(
|
||||||
|
path_key(std::path::Path::new(spelling)),
|
||||||
|
key,
|
||||||
|
"同一安装的两种写法必须同键:{spelling}"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn path_key_folds_case_only_where_the_filesystem_does() {
|
||||||
|
// 大小写折叠跟平台走,两侧都钉住:NTFS 不区分大小写 → 折;
|
||||||
|
// POSIX 上 `Java` 与 `java` 是两个目录 → 不折(折了会把两份真实安装误并)。
|
||||||
|
let upper = path_key(std::path::Path::new(
|
||||||
|
"C:/Program Files/Java/jdk-21/bin/java.exe",
|
||||||
|
));
|
||||||
|
let lower = path_key(std::path::Path::new(
|
||||||
|
"c:/program files/java/jdk-21/bin/java.exe",
|
||||||
|
));
|
||||||
|
if cfg!(windows) {
|
||||||
|
assert_eq!(upper, lower, "Windows:大小写不同的同一路径应同键");
|
||||||
|
} else {
|
||||||
|
assert_ne!(upper, lower, "POSIX:大小写是有效区别,不应同键");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
@@ -96,17 +96,20 @@ fn scanned_java_is_still_used_when_nothing_else_matches() {
|
|||||||
#[test]
|
#[test]
|
||||||
fn dedupe_collapses_one_install_found_twice() {
|
fn dedupe_collapses_one_install_found_twice() {
|
||||||
// `JAVA_HOME` 指向 Program Files 里的 JDK → 同一个 java 不应列两次。
|
// `JAVA_HOME` 指向 Program Files 里的 JDK → 同一个 java 不应列两次。
|
||||||
|
// 第二条的「另一种写法」随平台给:Windows 上大小写和分隔符都可能不一致(NTFS 不区分
|
||||||
|
// 大小写),POSIX 上大小写是有效区别,只能靠分隔符拼出同一路径的第二种写法。
|
||||||
|
let duplicate = if cfg!(windows) {
|
||||||
|
"c:/program files/java/jdk-21/bin/java.exe"
|
||||||
|
} else {
|
||||||
|
"C:\\Program Files\\Java\\jdk-21/bin/java.exe"
|
||||||
|
};
|
||||||
let candidates = discover::dedupe(vec![
|
let candidates = discover::dedupe(vec![
|
||||||
runtime(
|
runtime(
|
||||||
"C:/Program Files/Java/jdk-21/bin/java.exe",
|
"C:/Program Files/Java/jdk-21/bin/java.exe",
|
||||||
21,
|
21,
|
||||||
JavaOrigin::JavaHome,
|
JavaOrigin::JavaHome,
|
||||||
),
|
),
|
||||||
runtime(
|
runtime(duplicate, 21, JavaOrigin::System),
|
||||||
"c:/program files/java/jdk-21/bin/java.exe",
|
|
||||||
21,
|
|
||||||
JavaOrigin::System,
|
|
||||||
),
|
|
||||||
]);
|
]);
|
||||||
assert_eq!(candidates.len(), 1);
|
assert_eq!(candidates.len(), 1);
|
||||||
assert_eq!(candidates[0].origin, JavaOrigin::JavaHome);
|
assert_eq!(candidates[0].origin, JavaOrigin::JavaHome);
|
||||||
|
|||||||
Reference in new issue
Block a user