setup.dart 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412
  1. // ignore_for_file: avoid_print
  2. import 'dart:convert';
  3. import 'dart:io';
  4. import 'package:args/command_runner.dart';
  5. import 'package:path/path.dart';
  6. enum PlatformType {
  7. windows,
  8. linux,
  9. android,
  10. macos,
  11. }
  12. enum Arch { amd64, arm64, arm }
  13. class BuildLibItem {
  14. PlatformType platform;
  15. Arch arch;
  16. String archName;
  17. BuildLibItem({
  18. required this.platform,
  19. required this.arch,
  20. required this.archName,
  21. });
  22. String get dynamicLibExtensionName {
  23. final String extensionName;
  24. switch (platform) {
  25. case PlatformType.android || PlatformType.linux:
  26. extensionName = "so";
  27. break;
  28. case PlatformType.windows:
  29. extensionName = "dll";
  30. break;
  31. case PlatformType.macos:
  32. extensionName = "dylib";
  33. break;
  34. }
  35. return extensionName;
  36. }
  37. String get os {
  38. if (platform == PlatformType.macos) {
  39. return "darwin";
  40. }
  41. return platform.name;
  42. }
  43. @override
  44. String toString() {
  45. return 'BuildLibItem{platform: $platform, arch: $arch, archName: $archName}';
  46. }
  47. }
  48. class Build {
  49. static List<BuildLibItem> get buildItems => [
  50. BuildLibItem(
  51. platform: PlatformType.macos,
  52. arch: Arch.amd64,
  53. archName: '',
  54. ),
  55. BuildLibItem(
  56. platform: PlatformType.macos,
  57. arch: Arch.arm64,
  58. archName: '',
  59. ),
  60. BuildLibItem(
  61. platform: PlatformType.windows,
  62. arch: Arch.amd64,
  63. archName: '',
  64. ),
  65. BuildLibItem(
  66. platform: PlatformType.windows,
  67. arch: Arch.arm64,
  68. archName: '',
  69. ),
  70. BuildLibItem(
  71. platform: PlatformType.android,
  72. arch: Arch.arm,
  73. archName: 'armeabi-v7a',
  74. ),
  75. BuildLibItem(
  76. platform: PlatformType.android,
  77. arch: Arch.arm64,
  78. archName: 'arm64-v8a',
  79. ),
  80. BuildLibItem(
  81. platform: PlatformType.android,
  82. arch: Arch.amd64,
  83. archName: 'x86_64',
  84. ),
  85. BuildLibItem(
  86. platform: PlatformType.linux,
  87. arch: Arch.amd64,
  88. archName: '',
  89. ),
  90. ];
  91. static String get appName => "FlClash";
  92. static String get libName => "libclash";
  93. static String get outDir => join(current, libName);
  94. static String get _coreDir => join(current, "core");
  95. static String get distPath => join(current, "dist");
  96. static String _getCc(BuildLibItem buildItem) {
  97. final environment = Platform.environment;
  98. if (buildItem.platform == PlatformType.android) {
  99. final ndk = environment["ANDROID_NDK"];
  100. assert(ndk != null);
  101. final prebuiltDir =
  102. Directory(join(ndk!, "toolchains", "llvm", "prebuilt"));
  103. final prebuiltDirList = prebuiltDir.listSync();
  104. final map = {
  105. "armeabi-v7a": "armv7a-linux-androideabi21-clang",
  106. "arm64-v8a": "aarch64-linux-android21-clang",
  107. "x86": "i686-linux-android21-clang",
  108. "x86_64": "x86_64-linux-android21-clang"
  109. };
  110. return join(
  111. prebuiltDirList.first.path,
  112. "bin",
  113. map[buildItem.archName],
  114. );
  115. }
  116. return "gcc";
  117. }
  118. static get tags => "with_gvisor";
  119. static Future<void> exec(
  120. List<String> executable, {
  121. String? name,
  122. Map<String, String>? environment,
  123. String? workingDirectory,
  124. bool runInShell = true,
  125. }) async {
  126. if (name != null) print("run $name");
  127. final process = await Process.start(
  128. executable[0],
  129. executable.sublist(1),
  130. environment: environment,
  131. workingDirectory: workingDirectory,
  132. runInShell: runInShell,
  133. );
  134. process.stdout.listen((data) {
  135. print(utf8.decode(data));
  136. });
  137. process.stderr.listen((data) {
  138. print(utf8.decode(data));
  139. });
  140. final exitCode = await process.exitCode;
  141. if (exitCode != 0 && name != null) throw "$name error";
  142. }
  143. static buildLib({
  144. required PlatformType platform,
  145. Arch? arch,
  146. }) async {
  147. final items = buildItems.where(
  148. (element) {
  149. return element.platform == platform &&
  150. (arch == null ? true : element.arch == arch);
  151. },
  152. ).toList();
  153. for (final item in items) {
  154. final outFileDir = join(
  155. outDir,
  156. item.platform.name,
  157. item.archName,
  158. );
  159. final file = File(outFileDir);
  160. if (file.existsSync()) {
  161. file.deleteSync(recursive: true);
  162. }
  163. final outPath = join(
  164. outFileDir,
  165. "$libName.${item.dynamicLibExtensionName}",
  166. );
  167. final Map<String, String> env = {};
  168. env["GOOS"] = item.os;
  169. env["GOARCH"] = item.arch.name;
  170. env["CGO_ENABLED"] = "1";
  171. env["CC"] = _getCc(item);
  172. await exec(
  173. [
  174. "go",
  175. "build",
  176. "-ldflags=-w -s",
  177. "-tags=$tags",
  178. "-buildmode=c-shared",
  179. "-o",
  180. outPath,
  181. ],
  182. name: "build libclash",
  183. environment: env,
  184. workingDirectory: _coreDir,
  185. );
  186. }
  187. }
  188. static List<String> getExecutable(String command) {
  189. return command.split(" ");
  190. }
  191. static getDistributor() async {
  192. final distributorDir = join(
  193. current,
  194. "plugins",
  195. "flutter_distributor",
  196. "packages",
  197. "flutter_distributor",
  198. );
  199. await exec(
  200. name: "clean distributor",
  201. Build.getExecutable("flutter clean"),
  202. workingDirectory: distributorDir,
  203. );
  204. await exec(
  205. name: "get distributor",
  206. Build.getExecutable("dart pub global activate -s path $distributorDir"),
  207. );
  208. }
  209. static copyFile(String sourceFilePath, String destinationFilePath) {
  210. final sourceFile = File(sourceFilePath);
  211. if (!sourceFile.existsSync()) {
  212. throw "SourceFilePath not exists";
  213. }
  214. final destinationFile = File(destinationFilePath);
  215. final destinationDirectory = destinationFile.parent;
  216. if (!destinationDirectory.existsSync()) {
  217. destinationDirectory.createSync(recursive: true);
  218. }
  219. try {
  220. sourceFile.copySync(destinationFilePath);
  221. print("File copied successfully!");
  222. } catch (e) {
  223. print("Failed to copy file: $e");
  224. }
  225. }
  226. }
  227. class BuildCommand extends Command {
  228. PlatformType platform;
  229. BuildCommand({
  230. required this.platform,
  231. }) {
  232. argParser.addOption(
  233. "build",
  234. valueHelp: [
  235. 'all',
  236. 'lib',
  237. ].join(','),
  238. help: 'The $name build type',
  239. );
  240. argParser.addOption(
  241. "arch",
  242. valueHelp: arches.map((e) => e.name).join(','),
  243. help: 'The $name build arch',
  244. );
  245. }
  246. @override
  247. String get description => "build $name application";
  248. @override
  249. String get name => platform.name;
  250. List<Arch> get arches => Build.buildItems
  251. .where((element) => element.platform == platform)
  252. .map((e) => e.arch)
  253. .toList();
  254. Future<void> _buildLib(Arch? arch) async {
  255. await Build.buildLib(platform: platform, arch: arch);
  256. }
  257. _getLinuxDependencies() async {
  258. await Build.exec(
  259. Build.getExecutable("sudo apt update -y"),
  260. );
  261. await Build.exec(
  262. Build.getExecutable("sudo apt install -y ninja-build libgtk-3-dev"),
  263. );
  264. await Build.exec(
  265. Build.getExecutable("sudo apt install -y libayatana-appindicator3-dev"),
  266. );
  267. await Build.exec(
  268. Build.getExecutable("sudo apt install -y rpm patchelf"),
  269. );
  270. await Build.exec(
  271. Build.getExecutable("sudo apt install -y locate"),
  272. );
  273. await Build.exec(
  274. Build.getExecutable("sudo apt install -y libfuse2"),
  275. );
  276. await Build.exec(
  277. Build.getExecutable(
  278. "wget -O appimagetool https://github.com/AppImage/AppImageKit/releases/download/continuous/appimagetool-x86_64.AppImage",
  279. ),
  280. );
  281. await Build.exec(
  282. Build.getExecutable(
  283. "chmod +x appimagetool",
  284. ),
  285. );
  286. await Build.exec(
  287. Build.getExecutable(
  288. "sudo mv appimagetool /usr/local/bin/",
  289. ),
  290. );
  291. }
  292. _getMacosDependencies() async {
  293. await Build.exec(
  294. Build.getExecutable("npm install -g appdmg"),
  295. );
  296. }
  297. _buildDistributor({
  298. required PlatformType platform,
  299. required String targets,
  300. String args = '',
  301. }) async {
  302. await Build.getDistributor();
  303. await Build.exec(
  304. name: name,
  305. Build.getExecutable(
  306. "flutter_distributor package --skip-clean --platform ${platform.name} --targets $targets --flutter-build-args=verbose $args",
  307. ),
  308. );
  309. }
  310. @override
  311. Future<void> run() async {
  312. final String build = argResults?['build'] ?? 'all';
  313. final archName = argResults?['arch'];
  314. final currentArches =
  315. arches.where((element) => element.name == archName).toList();
  316. final arch = currentArches.isEmpty ? null : currentArches.first;
  317. if (arch == null && platform == PlatformType.windows) {
  318. throw "Invalid arch";
  319. }
  320. await _buildLib(arch);
  321. if (build != "all") {
  322. return;
  323. }
  324. switch (platform) {
  325. case PlatformType.windows:
  326. _buildDistributor(
  327. platform: platform,
  328. targets: "exe,zip",
  329. args: "--description ${arch!.name}",
  330. );
  331. case PlatformType.linux:
  332. await _getLinuxDependencies();
  333. _buildDistributor(
  334. platform: platform,
  335. targets: "appimage,deb,rpm",
  336. args: "--description ${arch!.name}",
  337. );
  338. case PlatformType.android:
  339. final targetMap = {
  340. Arch.arm: "android-arm",
  341. Arch.arm64: "android-arm64",
  342. Arch.amd64: "android-x64",
  343. };
  344. final defaultArches = [Arch.arm, Arch.arm64, Arch.amd64];
  345. final defaultTargets = defaultArches
  346. .where((element) => arch == null ? true : element == arch)
  347. .map((e) => targetMap[e])
  348. .toList();
  349. _buildDistributor(
  350. platform: platform,
  351. targets: "apk",
  352. args:
  353. "--flutter-build-args split-per-abi --build-target-platform ${defaultTargets.join(",")}",
  354. );
  355. case PlatformType.macos:
  356. await _getMacosDependencies();
  357. _buildDistributor(
  358. platform: platform,
  359. targets: "dmg",
  360. args: "--description ${arch!.name}",
  361. );
  362. }
  363. }
  364. }
  365. main(args) async {
  366. final runner = CommandRunner("setup", "build Application");
  367. runner.addCommand(BuildCommand(platform: PlatformType.android));
  368. if (Platform.isWindows) {
  369. runner.addCommand(BuildCommand(platform: PlatformType.windows));
  370. }
  371. if (Platform.isLinux) {
  372. runner.addCommand(BuildCommand(platform: PlatformType.linux));
  373. }
  374. if (Platform.isMacOS) {
  375. runner.addCommand(BuildCommand(platform: PlatformType.macos));
  376. }
  377. runner.run(args);
  378. }