554 lines
14 KiB
Dart
Executable File
554 lines
14 KiB
Dart
Executable File
// ignore_for_file: avoid_print
|
|
|
|
import 'dart:convert';
|
|
import 'dart:io';
|
|
|
|
import 'package:args/command_runner.dart';
|
|
import 'package:path/path.dart';
|
|
|
|
enum Target {
|
|
windows,
|
|
linux,
|
|
android,
|
|
macos,
|
|
}
|
|
|
|
extension TargetExt on Target {
|
|
String get os {
|
|
if (this == Target.macos) {
|
|
return "darwin";
|
|
}
|
|
return name;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
String get dynamicLibExtensionName {
|
|
final String extensionName;
|
|
switch (this) {
|
|
case Target.android || Target.linux:
|
|
extensionName = ".so";
|
|
break;
|
|
case Target.windows:
|
|
extensionName = ".dll";
|
|
break;
|
|
case Target.macos:
|
|
extensionName = ".dylib";
|
|
break;
|
|
}
|
|
return extensionName;
|
|
}
|
|
|
|
String get executableExtensionName {
|
|
final String extensionName;
|
|
switch (this) {
|
|
case Target.windows:
|
|
extensionName = ".exe";
|
|
break;
|
|
default:
|
|
extensionName = "";
|
|
break;
|
|
}
|
|
return extensionName;
|
|
}
|
|
}
|
|
|
|
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}';
|
|
}
|
|
}
|
|
|
|
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,
|
|
),
|
|
BuildItem(
|
|
target: Target.android,
|
|
arch: Arch.arm,
|
|
archName: 'armeabi-v7a',
|
|
),
|
|
BuildItem(
|
|
target: Target.android,
|
|
arch: Arch.arm64,
|
|
archName: 'arm64-v8a',
|
|
),
|
|
BuildItem(
|
|
target: Target.android,
|
|
arch: Arch.amd64,
|
|
archName: 'x86_64',
|
|
),
|
|
];
|
|
|
|
static String get appName => "FlClash";
|
|
|
|
static String get coreName => "FlClashCore";
|
|
|
|
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");
|
|
|
|
static String get distPath => join(current, "dist");
|
|
|
|
static String _getCc(BuildItem buildItem) {
|
|
final environment = Platform.environment;
|
|
if (buildItem.target == Target.android) {
|
|
final ndk = environment["ANDROID_NDK"];
|
|
assert(ndk != null);
|
|
final prebuiltDir =
|
|
Directory(join(ndk!, "toolchains", "llvm", "prebuilt"));
|
|
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, {
|
|
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,
|
|
Arch? arch,
|
|
}) async {
|
|
final isLib = mode == Mode.lib;
|
|
|
|
final items = buildItems.where(
|
|
(element) {
|
|
return element.target == target &&
|
|
(arch == null ? true : element.arch == arch);
|
|
},
|
|
).toList();
|
|
|
|
for (final item in items) {
|
|
final outFileDir = join(
|
|
outDir,
|
|
item.target.name,
|
|
item.archName,
|
|
);
|
|
|
|
final file = File(outFileDir);
|
|
if (file.existsSync()) {
|
|
file.deleteSync(recursive: true);
|
|
}
|
|
|
|
final fileName = isLib
|
|
? "$libName${item.target.dynamicLibExtensionName}"
|
|
: "$coreName${item.target.executableExtensionName}";
|
|
final outPath = join(
|
|
outFileDir,
|
|
fileName,
|
|
);
|
|
|
|
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";
|
|
}
|
|
|
|
final execLines = [
|
|
"go",
|
|
"build",
|
|
"-ldflags=-w -s",
|
|
"-tags=$tags",
|
|
if (isLib) "-buildmode=c-shared",
|
|
"-o",
|
|
outPath,
|
|
];
|
|
await exec(
|
|
execLines,
|
|
name: "build core",
|
|
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);
|
|
}
|
|
|
|
static List<String> getExecutable(String command) {
|
|
print(command);
|
|
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,
|
|
);
|
|
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;
|
|
|
|
BuildCommand({
|
|
required this.target,
|
|
}) {
|
|
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',
|
|
);
|
|
}
|
|
argParser.addOption(
|
|
"out",
|
|
valueHelp: [
|
|
if (target.same) "app",
|
|
"core",
|
|
].join(','),
|
|
help: 'The $name build arch',
|
|
);
|
|
argParser.addOption(
|
|
"env",
|
|
valueHelp: [
|
|
"pre",
|
|
"stable",
|
|
].join(','),
|
|
help: 'The $name build env',
|
|
);
|
|
}
|
|
|
|
@override
|
|
String get description => "build $name application";
|
|
|
|
@override
|
|
String get name => target.name;
|
|
|
|
List<Arch> get arches => Build.buildItems
|
|
.where((element) => element.target == target && element.arch != null)
|
|
.map((e) => e.arch!)
|
|
.toList();
|
|
|
|
_getLinuxDependencies(Arch arch) async {
|
|
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-get install -y libkeybinder-3.0-dev"),
|
|
);
|
|
await Build.exec(
|
|
Build.getExecutable("sudo apt install -y locate"),
|
|
);
|
|
if (arch == Arch.amd64) {
|
|
await Build.exec(
|
|
Build.getExecutable("sudo apt install -y rpm patchelf"),
|
|
);
|
|
await Build.exec(
|
|
Build.getExecutable("sudo apt install -y libfuse2"),
|
|
);
|
|
final downloadName = arch == Arch.amd64 ? "x86_64" : "aarch_64";
|
|
await Build.exec(
|
|
Build.getExecutable(
|
|
"wget -O appimagetool https://github.com/AppImage/AppImageKit/releases/download/continuous/appimagetool-$downloadName.AppImage",
|
|
),
|
|
);
|
|
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,
|
|
required String targets,
|
|
String args = '',
|
|
required String env,
|
|
}) 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 --build-dart-define=APP_ENV=$env",
|
|
),
|
|
);
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
@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 env = argResults?["env"] ?? "pre";
|
|
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") {
|
|
return;
|
|
}
|
|
|
|
switch (target) {
|
|
case Target.windows:
|
|
_buildDistributor(
|
|
target: target,
|
|
targets: "exe,zip",
|
|
args: "--description $archName",
|
|
env: env,
|
|
);
|
|
return;
|
|
case Target.linux:
|
|
final targetMap = {
|
|
Arch.arm64: "linux-arm64",
|
|
Arch.amd64: "linux-x64",
|
|
};
|
|
final targets = [
|
|
"deb",
|
|
if (arch == Arch.amd64) ...[
|
|
"appimage",
|
|
"rpm",
|
|
],
|
|
].join(",");
|
|
final defaultTarget = targetMap[arch];
|
|
await _getLinuxDependencies(arch!);
|
|
_buildDistributor(
|
|
target: target,
|
|
targets: targets,
|
|
args:
|
|
"--description $archName --build-target-platform $defaultTarget",
|
|
env: env,
|
|
);
|
|
return;
|
|
case Target.android:
|
|
final targetMap = {
|
|
Arch.arm: "android-arm",
|
|
Arch.arm64: "android-arm64",
|
|
Arch.amd64: "android-x64",
|
|
};
|
|
final defaultArches = [Arch.arm, Arch.arm64, Arch.amd64];
|
|
final defaultTargets = defaultArches
|
|
.where((element) => arch == null ? true : element == arch)
|
|
.map((e) => targetMap[e])
|
|
.toList();
|
|
_buildDistributor(
|
|
target: target,
|
|
targets: "apk",
|
|
args:
|
|
"--flutter-build-args split-per-abi --build-target-platform ${defaultTargets.join(",")}",
|
|
env: env,
|
|
);
|
|
return;
|
|
case Target.macos:
|
|
await _getMacosDependencies();
|
|
_buildDistributor(
|
|
target: target,
|
|
targets: "dmg",
|
|
args: "--description $archName",
|
|
env: env,
|
|
);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
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));
|
|
runner.run(args);
|
|
}
|