Files
MWClash/setup.dart

531 lines
13 KiB
Dart
Raw Normal View History

2024-04-30 23:38:49 +08:00
// ignore_for_file: avoid_print
import 'dart:convert';
import 'dart:io';
2024-04-30 23:38:49 +08:00
import 'package:args/command_runner.dart';
import 'package:path/path.dart';
enum Target {
2024-04-30 23:38:49 +08:00
windows,
linux,
android,
macos,
}
extension TargetExt on Target {
String get os {
if (this == Target.macos) {
return "darwin";
}
return name;
}
2024-04-30 23:38:49 +08:00
bool get same {
if (this == Target.android) {
return true;
}
if (Platform.isWindows && this == Target.windows) {
return true;
}
if (Platform.isLinux && this == Target.linux) {
return true;
}
if (Platform.isMacOS && this == Target.macos) {
return true;
}
return false;
}
2024-04-30 23:38:49 +08:00
String get dynamicLibExtensionName {
final String extensionName;
switch (this) {
case Target.android || Target.linux:
extensionName = ".so";
2024-04-30 23:38:49 +08:00
break;
case Target.windows:
extensionName = ".dll";
2024-04-30 23:38:49 +08:00
break;
case Target.macos:
extensionName = ".dylib";
2024-04-30 23:38:49 +08:00
break;
}
return extensionName;
}
String get executableExtensionName {
final String extensionName;
switch (this) {
case Target.windows:
extensionName = ".exe";
break;
default:
extensionName = "";
break;
2024-04-30 23:38:49 +08:00
}
return extensionName;
2024-04-30 23:38:49 +08:00
}
}
enum Mode { core, lib }
enum Arch { amd64, arm64, arm }
class BuildItem {
Target target;
Arch? arch;
String? archName;
BuildItem({
required this.target,
this.arch,
this.archName,
});
@override
String toString() {
return 'BuildLibItem{target: $target, arch: $arch, archName: $archName}';
}
2024-04-30 23:38:49 +08:00
}
class Build {
static List<BuildItem> get buildItems => [
BuildItem(
target: Target.macos,
arch: Arch.arm64,
),
BuildItem(
target: Target.macos,
arch: Arch.amd64,
),
BuildItem(
target: Target.linux,
arch: Arch.arm64,
),
BuildItem(
target: Target.linux,
arch: Arch.amd64,
),
BuildItem(
target: Target.windows,
arch: Arch.amd64,
),
BuildItem(
target: Target.windows,
arch: Arch.arm64,
2024-04-30 23:38:49 +08:00
),
BuildItem(
target: Target.android,
arch: Arch.arm,
archName: 'armeabi-v7a',
),
BuildItem(
target: Target.android,
2024-04-30 23:38:49 +08:00
arch: Arch.arm64,
archName: 'arm64-v8a',
),
BuildItem(
target: Target.android,
arch: Arch.amd64,
archName: 'x86_64',
),
2024-04-30 23:38:49 +08:00
];
static String get appName => "FlClash";
static String get coreName => "FlClashCore";
2024-04-30 23:38:49 +08:00
static String get libName => "libclash";
static String get outDir => join(current, libName);
static String get _coreDir => join(current, "core");
static String get _servicesDir => join(current, "services", "helper");
2024-04-30 23:38:49 +08:00
static String get distPath => join(current, "dist");
static String _getCc(BuildItem buildItem) {
2024-04-30 23:38:49 +08:00
final environment = Platform.environment;
if (buildItem.target == Target.android) {
2024-04-30 23:38:49 +08:00
final ndk = environment["ANDROID_NDK"];
assert(ndk != null);
final prebuiltDir =
Directory(join(ndk!, "toolchains", "llvm", "prebuilt"));
2024-04-30 23:38:49 +08:00
final prebuiltDirList = prebuiltDir.listSync();
final map = {
"armeabi-v7a": "armv7a-linux-androideabi21-clang",
"arm64-v8a": "aarch64-linux-android21-clang",
"x86": "i686-linux-android21-clang",
"x86_64": "x86_64-linux-android21-clang"
};
return join(
prebuiltDirList.first.path,
"bin",
map[buildItem.archName],
);
}
return "gcc";
}
static get tags => "with_gvisor";
static Future<void> exec(
List<String> executable, {
2024-04-30 23:38:49 +08:00
String? name,
Map<String, String>? environment,
String? workingDirectory,
bool runInShell = true,
}) async {
if (name != null) print("run $name");
final process = await Process.start(
executable[0],
executable.sublist(1),
environment: environment,
workingDirectory: workingDirectory,
runInShell: runInShell,
);
process.stdout.listen((data) {
print(utf8.decode(data));
});
process.stderr.listen((data) {
print(utf8.decode(data));
});
final exitCode = await process.exitCode;
if (exitCode != 0 && name != null) throw "$name error";
}
static buildCore({
required Mode mode,
required Target target,
2024-04-30 23:38:49 +08:00
Arch? arch,
}) async {
final isLib = mode == Mode.lib;
2024-04-30 23:38:49 +08:00
final items = buildItems.where(
(element) {
return element.target == target &&
(arch == null ? true : element.arch == arch);
2024-04-30 23:38:49 +08:00
},
).toList();
2024-04-30 23:38:49 +08:00
for (final item in items) {
final outFileDir = join(
outDir,
item.target.name,
2024-04-30 23:38:49 +08:00
item.archName,
);
2024-04-30 23:38:49 +08:00
final file = File(outFileDir);
if (file.existsSync()) {
file.deleteSync(recursive: true);
}
final fileName = isLib
? "$libName${item.target.dynamicLibExtensionName}"
: "$coreName${item.target.executableExtensionName}";
2024-04-30 23:38:49 +08:00
final outPath = join(
outFileDir,
fileName,
2024-04-30 23:38:49 +08:00
);
2024-04-30 23:38:49 +08:00
final Map<String, String> env = {};
env["GOOS"] = item.target.os;
if (item.arch != null) {
env["GOARCH"] = item.arch!.name;
}
if (isLib) {
env["CGO_ENABLED"] = "1";
env["CC"] = _getCc(item);
env["CFLAGS"] = "-O3 -Werror";
} else {
env["CGO_ENABLED"] = "0";
}
2024-04-30 23:38:49 +08:00
final execLines = [
"go",
"build",
"-ldflags=-w -s",
"-tags=$tags",
if (isLib) "-buildmode=c-shared",
"-o",
outPath,
];
2024-04-30 23:38:49 +08:00
await exec(
execLines,
name: "build core",
2024-04-30 23:38:49 +08:00
environment: env,
workingDirectory: _coreDir,
);
}
}
static buildHelper(Target target) async {
await exec(
[
"cargo",
"build",
"--release",
"--features",
"windows-service",
],
name: "build helper",
workingDirectory: _servicesDir,
);
final outPath = join(
_servicesDir,
"target",
"release",
"helper${target.executableExtensionName}",
);
final targetPath = join(outDir, target.name,
"FlClashHelperService${target.executableExtensionName}");
await File(outPath).copy(targetPath);
}
2024-04-30 23:38:49 +08:00
static List<String> getExecutable(String command) {
print(command);
2024-04-30 23:38:49 +08:00
return command.split(" ");
}
static getDistributor() async {
final distributorDir = join(
current,
"plugins",
"flutter_distributor",
"packages",
"flutter_distributor",
);
await exec(
name: "clean distributor",
Build.getExecutable("flutter clean"),
workingDirectory: distributorDir,
);
await exec(
name: "upgrade distributor",
Build.getExecutable("flutter pub upgrade"),
workingDirectory: distributorDir,
);
2024-04-30 23:38:49 +08:00
await exec(
name: "get distributor",
Build.getExecutable("dart pub global activate -s path $distributorDir"),
);
}
static copyFile(String sourceFilePath, String destinationFilePath) {
final sourceFile = File(sourceFilePath);
if (!sourceFile.existsSync()) {
throw "SourceFilePath not exists";
}
final destinationFile = File(destinationFilePath);
final destinationDirectory = destinationFile.parent;
if (!destinationDirectory.existsSync()) {
destinationDirectory.createSync(recursive: true);
}
try {
sourceFile.copySync(destinationFilePath);
print("File copied successfully!");
} catch (e) {
print("Failed to copy file: $e");
}
}
}
class BuildCommand extends Command {
Target target;
2024-04-30 23:38:49 +08:00
BuildCommand({
required this.target,
2024-04-30 23:38:49 +08:00
}) {
if (target == Target.android || target == Target.linux) {
argParser.addOption(
"arch",
valueHelp: arches.map((e) => e.name).join(','),
help: 'The $name build desc',
);
} else {
argParser.addOption(
"arch",
help: 'The $name build archName',
);
}
2024-04-30 23:38:49 +08:00
argParser.addOption(
"out",
2024-04-30 23:38:49 +08:00
valueHelp: [
if (target.same) "app",
"core",
2024-04-30 23:38:49 +08:00
].join(','),
help: 'The $name build arch',
);
}
@override
String get description => "build $name application";
@override
String get name => target.name;
2024-04-30 23:38:49 +08:00
List<Arch> get arches => Build.buildItems
.where((element) => element.target == target && element.arch != null)
.map((e) => e.arch!)
.toList();
2024-04-30 23:38:49 +08:00
_getLinuxDependencies(Arch arch) async {
2024-04-30 23:38:49 +08:00
await Build.exec(
Build.getExecutable("sudo apt update -y"),
);
await Build.exec(
Build.getExecutable("sudo apt install -y ninja-build libgtk-3-dev"),
);
await Build.exec(
Build.getExecutable("sudo apt install -y libayatana-appindicator3-dev"),
);
await Build.exec(
Build.getExecutable("sudo apt install -y rpm patchelf"),
);
await Build.exec(
Build.getExecutable("sudo apt-get install -y libkeybinder-3.0-dev"),
);
2024-04-30 23:38:49 +08:00
await Build.exec(
Build.getExecutable("sudo apt install -y locate"),
);
await Build.exec(
Build.getExecutable("sudo apt install -y libfuse2"),
);
final downloadName = arch == Arch.amd64 ? "x86_64" : "aarch_64";
2024-04-30 23:38:49 +08:00
await Build.exec(
Build.getExecutable(
"wget -O appimagetool https://github.com/AppImage/AppImageKit/releases/download/continuous/appimagetool-$downloadName.AppImage",
2024-04-30 23:38:49 +08:00
),
);
await Build.exec(
Build.getExecutable(
"chmod +x appimagetool",
),
);
await Build.exec(
Build.getExecutable(
"sudo mv appimagetool /usr/local/bin/",
),
);
}
_getMacosDependencies() async {
await Build.exec(
Build.getExecutable("npm install -g appdmg"),
);
}
_buildDistributor({
required Target target,
2024-04-30 23:38:49 +08:00
required String targets,
String args = '',
}) async {
await Build.getDistributor();
await Build.exec(
name: name,
Build.getExecutable(
"flutter_distributor package --skip-clean --platform ${target.name} --targets $targets --flutter-build-args=verbose $args",
2024-04-30 23:38:49 +08:00
),
);
}
Future<String?> get systemArch async {
if (Platform.isWindows) {
return Platform.environment["PROCESSOR_ARCHITECTURE"];
} else if (Platform.isLinux || Platform.isMacOS) {
final result = await Process.run('uname', ['-m']);
return result.stdout.toString().trim();
}
return null;
}
2024-04-30 23:38:49 +08:00
@override
Future<void> run() async {
final mode = target == Target.android ? Mode.lib : Mode.core;
final String out = argResults?["out"] ?? (target.same ? "app" : "core");
final archName = argResults?["arch"];
final currentArches =
arches.where((element) => element.name == archName).toList();
final arch = currentArches.isEmpty ? null : currentArches.first;
if (arch == null && target != Target.android) {
throw "Invalid arch parameter";
}
await Build.buildCore(
target: target,
arch: arch,
mode: mode,
);
if (target == Target.windows) {
await Build.buildHelper(target);
}
if (out != "app") {
2024-04-30 23:38:49 +08:00
return;
}
switch (target) {
case Target.windows:
2024-04-30 23:38:49 +08:00
_buildDistributor(
target: target,
2024-04-30 23:38:49 +08:00
targets: "exe,zip",
args: "--description $archName",
2024-04-30 23:38:49 +08:00
);
return;
case Target.linux:
final targetMap = {
Arch.arm64: "linux-arm64",
Arch.amd64: "linux-x64",
};
final defaultTarget = targetMap[arch];
await _getLinuxDependencies(arch!);
2024-04-30 23:38:49 +08:00
_buildDistributor(
target: target,
targets: "appimage,deb",
args:
"--description $archName --build-target-platform $defaultTarget",
2024-04-30 23:38:49 +08:00
);
return;
case Target.android:
2024-04-30 23:38:49 +08:00
final targetMap = {
Arch.arm: "android-arm",
Arch.arm64: "android-arm64",
2024-04-30 23:38:49 +08:00
Arch.amd64: "android-x64",
};
final defaultArches = [Arch.arm, Arch.arm64, Arch.amd64];
2024-04-30 23:38:49 +08:00
final defaultTargets = defaultArches
.where((element) => arch == null ? true : element == arch)
.map((e) => targetMap[e])
.toList();
_buildDistributor(
target: target,
2024-04-30 23:38:49 +08:00
targets: "apk",
args:
"--flutter-build-args split-per-abi --build-target-platform ${defaultTargets.join(",")}",
2024-04-30 23:38:49 +08:00
);
return;
case Target.macos:
2024-04-30 23:38:49 +08:00
await _getMacosDependencies();
_buildDistributor(
target: target,
2024-04-30 23:38:49 +08:00
targets: "dmg",
args: "--description $archName",
2024-04-30 23:38:49 +08:00
);
return;
2024-04-30 23:38:49 +08:00
}
}
}
main(args) async {
final runner = CommandRunner("setup", "build Application");
runner.addCommand(BuildCommand(target: Target.android));
runner.addCommand(BuildCommand(target: Target.linux));
runner.addCommand(BuildCommand(target: Target.windows));
runner.addCommand(BuildCommand(target: Target.macos));
2024-04-30 23:38:49 +08:00
runner.run(args);
}