129 lines
4.0 KiB
Dart
129 lines
4.0 KiB
Dart
import 'dart:io';
|
||
import 'package:path/path.dart' as p;
|
||
|
||
/// 路径工具类 - 获取应用根目录和 bin 目录
|
||
///
|
||
/// 约定:
|
||
/// - Windows 开发模式:`windows/bin` 目录下放置配置和数据文件
|
||
/// - Windows 发布模式:可执行文件同级目录下放置 `bin` 目录
|
||
/// - Linux 开发模式:项目根目录下放置 `bin` 目录
|
||
/// - Linux 发布模式:可执行文件同级目录下放置 `bin` 目录
|
||
class PathUtils {
|
||
static String? _cachedAppRoot;
|
||
|
||
/// 获取应用根目录
|
||
static String get appRoot {
|
||
if (_cachedAppRoot != null) {
|
||
return _cachedAppRoot!;
|
||
}
|
||
|
||
try {
|
||
final exePath = Platform.resolvedExecutable;
|
||
final exeDir = p.dirname(exePath);
|
||
|
||
if (Platform.isWindows) {
|
||
_cachedAppRoot = _resolveWindowsAppRoot(exeDir);
|
||
} else if (Platform.isLinux) {
|
||
_cachedAppRoot = _resolveLinuxAppRoot(exeDir);
|
||
} else {
|
||
// 其他平台暂时按当前工作目录 + bin 处理
|
||
_cachedAppRoot = _resolveGenericAppRoot(exeDir);
|
||
}
|
||
|
||
return _cachedAppRoot!;
|
||
} catch (_) {
|
||
// 兜底:使用当前工作目录
|
||
_cachedAppRoot = Directory.current.path;
|
||
return _cachedAppRoot!;
|
||
}
|
||
}
|
||
|
||
/// Windows 平台下解析根目录
|
||
static String _resolveWindowsAppRoot(String exeDir) {
|
||
// 开发模式:可执行文件在 build 目录下,需要回溯到 windows 目录
|
||
if (exeDir.contains('build') || exeDir.contains('windows\\build') || exeDir.contains('windows/build')) {
|
||
var currentPath = exeDir;
|
||
|
||
while (currentPath.isNotEmpty) {
|
||
final dirName = p.basename(currentPath);
|
||
final parent = p.dirname(currentPath);
|
||
|
||
if (dirName == 'windows' &&
|
||
!currentPath.contains('build\\windows') &&
|
||
!currentPath.contains('build/windows')) {
|
||
final binPath = p.join(currentPath, 'bin');
|
||
if (Directory(binPath).existsSync()) {
|
||
return currentPath;
|
||
}
|
||
}
|
||
|
||
if (parent == currentPath) break;
|
||
currentPath = parent;
|
||
}
|
||
|
||
// 回退到通过当前工作目录查找
|
||
final fromCwd = _findDirWithBinFromCwd('windows');
|
||
if (fromCwd != null) return fromCwd;
|
||
}
|
||
|
||
// 发布模式:可执行文件和 bin 文件夹在同一目录
|
||
return exeDir;
|
||
}
|
||
|
||
/// Linux 平台下解析根目录
|
||
static String _resolveLinuxAppRoot(String exeDir) {
|
||
// 如果可执行文件目录下有 bin,优先使用(发布模式)
|
||
final binInExeDir = p.join(exeDir, 'bin');
|
||
if (Directory(binInExeDir).existsSync()) {
|
||
return exeDir;
|
||
}
|
||
|
||
// 开发模式:从当前工作目录向上查找包含 bin 的目录(项目根目录)
|
||
final fromCwd = _findDirWithBinFromCwd(null);
|
||
if (fromCwd != null) return fromCwd;
|
||
|
||
// 再退回到 exeDir
|
||
return exeDir;
|
||
}
|
||
|
||
/// 通用平台兜底逻辑:优先找当前工作目录上的 bin
|
||
static String _resolveGenericAppRoot(String exeDir) {
|
||
final fromCwd = _findDirWithBinFromCwd(null);
|
||
if (fromCwd != null) return fromCwd;
|
||
return exeDir;
|
||
}
|
||
|
||
/// 从当前工作目录向上查找,直到找到满足条件的目录
|
||
/// 如果 [expectDirName] 不为空,则必须匹配目录名,例如 'windows'
|
||
static String? _findDirWithBinFromCwd(String? expectDirName) {
|
||
var currentPath = Directory.current.path;
|
||
|
||
while (currentPath.isNotEmpty) {
|
||
final dirName = p.basename(currentPath);
|
||
final binPath = p.join(currentPath, 'bin');
|
||
|
||
if ((expectDirName == null || dirName == expectDirName) &&
|
||
Directory(binPath).existsSync()) {
|
||
return currentPath;
|
||
}
|
||
|
||
final parent = p.dirname(currentPath);
|
||
if (parent == currentPath) break;
|
||
currentPath = parent;
|
||
}
|
||
|
||
return null;
|
||
}
|
||
|
||
/// 获取 bin 目录路径
|
||
static String get binDir {
|
||
final root = appRoot;
|
||
return p.join(root, 'bin');
|
||
}
|
||
|
||
/// 获取 bin 目录下的文件路径
|
||
static String binFile(String filename) {
|
||
return p.join(binDir, filename);
|
||
}
|
||
}
|