From 5c1fa7cc9e85f9d9119f0ae5e8e5cafb2296cd08 Mon Sep 17 00:00:00 2001 From: VillagerTom Date: Wed, 27 May 2026 00:15:36 +0800 Subject: [PATCH 1/9] =?UTF-8?q?refactor(build):=20=E5=B0=86=E5=AE=89?= =?UTF-8?q?=E8=A3=85=E4=BE=9D=E8=B5=96=E6=AD=A5=E9=AA=A4=E8=BD=AC=E7=A7=BB?= =?UTF-8?q?=E8=87=B3build.yaml?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/build.yaml | 21 +++++++++++++++++++-- setup.dart | 36 ------------------------------------ 2 files changed, 19 insertions(+), 38 deletions(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index e2e148067..4f6abfff5 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -141,7 +141,24 @@ jobs: if: matrix.platform == 'linux' run: | sudo apt-get update -o Acquire::Retries=3 -o Acquire::ForceIPv4=true - sudo apt-get install -y -o Acquire::Retries=3 -o Acquire::ForceIPv4=true --fix-missing libcurl4-openssl-dev ninja-build libgtk-3-dev rpm + sudo apt-get install -y -o Acquire::Retries=3 -o Acquire::ForceIPv4=true --fix-missing \ + libcurl4-openssl-dev \ + ninja-build \ + libgtk-3-dev \ + libayatana-appindicator3-dev \ + libkeybinder-3.0-dev \ + locate \ + ${{ matrix.arch == 'amd64' && 'rpm patchelf libfuse2' || '' }} + + _arch=${{ matrix.arch == 'amd64' && 'x86_64' || 'aarch64' }} + wget -O appimagetool https://github.com/AppImage/AppImageKit/releases/download/continuous/appimagetool-${_arch}.AppImage + chmod +x appimagetool + sudo mv appimagetool /usr/local/bin/ + + - name: Install Dependencies (macOS) + if: matrix.platform == 'macos' + run: | + npm install -g appdmg - name: Setup Golang uses: actions/setup-go@v6 @@ -462,4 +479,4 @@ jobs: with: files: ./dist/* body_path: './release.md' - prerelease: ${{ env.IS_PRERELEASE == 'true' }} \ No newline at end of file + prerelease: ${{ env.IS_PRERELEASE == 'true' }} diff --git a/setup.dart b/setup.dart index 2bb0ad7b8..d0b1cde0e 100644 --- a/setup.dart +++ b/setup.dart @@ -385,36 +385,6 @@ class BuildCommand extends Command { .map((e) => e.arch!) .toList(); - Future _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 libfuse2'), - ); - - final downloadName = arch == Arch.amd64 ? 'x86_64' : 'aarch64'; - 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/'), - ); - } - } - Future _setLinuxCoreSetuid() async { final coreFile = File('libclash/linux/BettboxCore'); if (!coreFile.existsSync()) return; @@ -423,10 +393,6 @@ class BuildCommand extends Command { } catch (_) {} } - Future _getMacosDependencies() async { - await Build.exec(Build.getExecutable('npm install -g appdmg')); - } - Future _setMacOSImpeller(bool enable) async { final infoPlistPath = 'macos/Runner/Info.plist'; final file = File(infoPlistPath); @@ -704,7 +670,6 @@ class BuildCommand extends Command { if (arch == Arch.amd64) 'rpm', ]; final defaultTarget = targetMap[arch]; - await _getLinuxDependencies(arch!); await _setLinuxCoreSetuid(); for (final t in targets) { final ext = t == 'appimage' ? 'AppImage' : t; @@ -745,7 +710,6 @@ class BuildCommand extends Command { ); return; case Target.macos: - await _getMacosDependencies(); await _setMacOSImpeller(!compatible); await Build.exec( Build.getExecutable('rm -rf Pods Podfile.lock'), From fcc88b1a6cef6375d766fc6e1cc9d0461e844c90 Mon Sep 17 00:00:00 2001 From: VillagerTom Date: Tue, 26 May 2026 23:51:51 +0800 Subject: [PATCH 2/9] =?UTF-8?q?chore(build):=20=E4=BF=AE=E6=94=B9=E6=98=93?= =?UTF-8?q?=E6=B7=B7=E6=B7=86=E7=9A=84=E5=8F=98=E9=87=8F=E5=90=8D=E7=A7=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - [target -> platform] 与 flutter_distributor 保持一致 - [Target -> TargetPlatform] 语义一致 - [Mode -> CoreMode] (顶级枚举) 表意更清晰 - [TargetPlatform.same -> TargetPlatform.same, TargetPlatform.buildable] 职责明确 --- setup.dart | 203 +++++++++++++++++++++++++++-------------------------- 1 file changed, 103 insertions(+), 100 deletions(-) diff --git a/setup.dart b/setup.dart index d0b1cde0e..35c2ea05f 100644 --- a/setup.dart +++ b/setup.dart @@ -7,42 +7,32 @@ import 'package:args/command_runner.dart'; import 'package:crypto/crypto.dart'; import 'package:path/path.dart'; -enum Target { windows, linux, android, macos } +enum TargetPlatform { windows, linux, android, macos } -extension TargetExt on Target { +extension PlatformExt on TargetPlatform { String get os { - if (this == Target.macos) { + if (this == TargetPlatform.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; + bool get same => name == Platform.operatingSystem; + + bool get buildable { + return same || this == TargetPlatform.android; } String get dynamicLibExtensionName { final String extensionName; switch (this) { - case Target.android || Target.linux: + case TargetPlatform.android || TargetPlatform.linux: extensionName = '.so'; break; - case Target.windows: + case TargetPlatform.windows: extensionName = '.dll'; break; - case Target.macos: + case TargetPlatform.macos: extensionName = '.dylib'; break; } @@ -52,7 +42,7 @@ extension TargetExt on Target { String get executableExtensionName { final String extensionName; switch (this) { - case Target.windows: + case TargetPlatform.windows: extensionName = '.exe'; break; default: @@ -63,20 +53,20 @@ extension TargetExt on Target { } } -enum Mode { core, lib } +enum CoreMode { core, lib } enum Arch { amd64, arm64, arm } class BuildItem { - Target target; + TargetPlatform platform; Arch? arch; String? archName; - BuildItem({required this.target, this.arch, this.archName}); + BuildItem({required this.platform, this.arch, this.archName}); @override String toString() { - return 'BuildLibItem{target: $target, arch: $arch, archName: $archName}'; + return 'BuildLibItem{platform: $platform, arch: $arch, archName: $archName}'; } } @@ -86,15 +76,27 @@ class Build { static String get identityName => isDev ? '${appName}Dev' : appName; static List 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'), + BuildItem(platform: TargetPlatform.macos, arch: Arch.arm64), + BuildItem(platform: TargetPlatform.macos, arch: Arch.amd64), + BuildItem(platform: TargetPlatform.linux, arch: Arch.arm64), + BuildItem(platform: TargetPlatform.linux, arch: Arch.amd64), + BuildItem(platform: TargetPlatform.windows, arch: Arch.amd64), + BuildItem(platform: TargetPlatform.windows, arch: Arch.arm64), + BuildItem( + platform: TargetPlatform.android, + arch: Arch.arm, + archName: 'armeabi-v7a', + ), + BuildItem( + platform: TargetPlatform.android, + arch: Arch.arm64, + archName: 'arm64-v8a', + ), + BuildItem( + platform: TargetPlatform.android, + arch: Arch.amd64, + archName: 'x86_64', + ), ]; static String get appName => 'Bettbox'; @@ -115,7 +117,7 @@ class Build { static String _getCc(BuildItem buildItem) { final environment = Platform.environment; - if (buildItem.target == Target.android) { + if (buildItem.platform == TargetPlatform.android) { final ndk = environment['ANDROID_NDK']; assert(ndk != null); final prebuiltDir = Directory( @@ -135,7 +137,7 @@ class Build { static String getTags(BuildItem buildItem) { final baseTags = 'with_gvisor'; - if (buildItem.target == Target.android && + if (buildItem.platform == TargetPlatform.android && buildItem.archName == 'armeabi-v7a') { return '$baseTags,with_low_memory'; } @@ -177,22 +179,22 @@ class Build { } static Future> buildCore({ - required Mode mode, - required Target target, + required CoreMode mode, + required TargetPlatform platform, Arch? arch, bool compatible = false, }) async { - final isLib = mode == Mode.lib; + final isLib = mode == CoreMode.lib; final items = buildItems.where((element) { - return element.target == target && + return element.platform == platform && (arch == null ? true : element.arch == arch); }).toList(); final List corePaths = []; for (final item in items) { - final outFileDir = join(outDir, item.target.name, item.archName); + final outFileDir = join(outDir, item.platform.name, item.archName); final file = File(outFileDir); if (file.existsSync()) { @@ -200,20 +202,20 @@ class Build { } final fileName = isLib - ? '$libName${item.target.dynamicLibExtensionName}' - : '$coreName${item.target.executableExtensionName}'; + ? '$libName${item.platform.dynamicLibExtensionName}' + : '$coreName${item.platform.executableExtensionName}'; final outPath = join(outFileDir, fileName); corePaths.add(outPath); final Map env = {}; - env['GOOS'] = item.target.os; + env['GOOS'] = item.platform.os; if (item.arch != null) { env['GOARCH'] = item.arch!.name; } if (item.arch == Arch.amd64 && - (item.target == Target.windows || - item.target == Target.linux || - item.target == Target.macos)) { + (item.platform == TargetPlatform.windows || + item.platform == TargetPlatform.linux || + item.platform == TargetPlatform.macos)) { env['GOAMD64'] = compatible ? 'v1' : 'v3'; } if (isLib) { @@ -237,7 +239,7 @@ class Build { 'go', 'build', '-trimpath', - '-ldflags=-w -s${item.target == Target.android && (item.arch == Arch.arm64 || item.arch == Arch.amd64) ? ' -extldflags "-Wl,-z,max-page-size=16384"' : ''}', + '-ldflags=-w -s${item.platform == TargetPlatform.android && (item.arch == Arch.arm64 || item.arch == Arch.amd64) ? ' -extldflags "-Wl,-z,max-page-size=16384"' : ''}', '-tags=$buildTags', if (isLib) '-buildmode=c-shared', '-o', @@ -254,7 +256,7 @@ class Build { return corePaths; } - static Future buildHelper(Target target, String token) async { + static Future buildHelper(TargetPlatform platform, String token) async { await exec( ['cargo', 'build', '--release', '--features', 'windows-service'], environment: {'TOKEN': token}, @@ -265,12 +267,12 @@ class Build { _servicesDir, 'target', 'release', - 'helper${target.executableExtensionName}', + 'helper${platform.executableExtensionName}', ); final targetPath = join( Build.outDir, - target.name, - '${Build.helperName}${target.executableExtensionName}', + platform.name, + '${Build.helperName}${platform.executableExtensionName}', ); await File(outPath).copy(targetPath); } @@ -324,14 +326,15 @@ class Build { } class BuildCommand extends Command { - Target target; + TargetPlatform platform; - BuildCommand({required this.target}) { - if (target == Target.android || target == Target.linux) { + BuildCommand({required this.platform}) { + if (platform == TargetPlatform.android || + platform == TargetPlatform.linux) { argParser.addOption( 'arch', valueHelp: [ - if (target != Target.android) 'auto', + if (platform != TargetPlatform.android) 'auto', ...arches.map((e) => e.name), ].join(','), help: 'The $name build desc', @@ -346,7 +349,7 @@ class BuildCommand extends Command { argParser.addOption( 'out', valueHelp: [ - if (target.same) 'app', + if (platform.buildable) 'app', 'core', 'core-only', 'helper', @@ -378,10 +381,10 @@ class BuildCommand extends Command { String get description => 'build $name application'; @override - String get name => target.name; + String get name => platform.name; List get arches => Build.buildItems - .where((element) => element.target == target && element.arch != null) + .where((element) => element.platform == platform && element.arch != null) .map((e) => e.arch!) .toList(); @@ -428,7 +431,7 @@ class BuildCommand extends Command { } Future _buildDistributor({ - required Target target, + required TargetPlatform platform, required String targets, String args = '', required String env, @@ -454,7 +457,7 @@ class BuildCommand extends Command { await Build.exec( name: name, Build.getExecutable( - 'flutter_distributor package --skip-clean --platform ${target.name} --targets $targets --flutter-build-args=verbose$args$sentryArg$suffixArg --build-dart-define=APP_ENV=$env$appDevArg', + 'flutter_distributor package --skip-clean --platform ${platform.name} --targets $targets --flutter-build-args=verbose$args$sentryArg$suffixArg --build-dart-define=APP_ENV=$env$appDevArg', ), environment: environment, ); @@ -481,28 +484,28 @@ class BuildCommand extends Command { List _expectedOutputs(Arch? arch) { final items = Build.buildItems.where((element) { - return element.target == target && + return element.platform == platform && (arch == null ? true : element.arch == arch); }); final outputs = []; for (final item in items) { - final outFileDir = join(Build.outDir, item.target.name, item.archName); - if (target == Target.android) { + final outFileDir = join(Build.outDir, item.platform.name, item.archName); + if (platform == TargetPlatform.android) { outputs.add(join(outFileDir, '${Build.libName}.so')); outputs.add(join(outFileDir, '${Build.libName}.h')); continue; } outputs.add( - join(outFileDir, '${Build.coreName}${target.executableExtensionName}'), + join(outFileDir, '${Build.coreName}${platform.executableExtensionName}'), ); - if (target == Target.windows) { + if (platform == TargetPlatform.windows) { outputs.add( join( outFileDir, - '${Build.helperName}${target.executableExtensionName}', + '${Build.helperName}${platform.executableExtensionName}', ), ); } @@ -540,7 +543,7 @@ class BuildCommand extends Command { return false; } - if (target == Target.windows) { + if (platform == TargetPlatform.windows) { final latestInput = _windowsSourcesLastModified(); final oldestOutput = outputs .map((path) => File(path).statSync().modified) @@ -553,17 +556,17 @@ class BuildCommand extends Command { @override Future run() async { - final mode = target == Target.android ? Mode.lib : Mode.core; - final String out = argResults?['out'] ?? (target.same ? 'app' : 'core'); + final coreMode = platform == TargetPlatform.android ? CoreMode.lib : CoreMode.core; + final String out = argResults?['out'] ?? (platform.buildable ? 'app' : 'core'); final env = argResults?['env'] ?? 'pre'; Build.isDev = argResults?['dev'] ?? false; String? archName = argResults?['arch']; if (archName == 'auto') { - if (target == Target.android) { + if (platform == TargetPlatform.android) { throw '--arch auto is not supported for android; choose the device ABI explicitly'; } - if (!target.same) { + if (!platform.same) { throw '--arch auto can only be used for the current host platform'; } archName = _mapHostArch(await systemArch); @@ -577,7 +580,7 @@ class BuildCommand extends Command { .toList(); final arch = currentArches.isEmpty ? null : currentArches.first; - if (arch == null && target != Target.android) { + if (arch == null && platform != TargetPlatform.android) { throw 'Invalid arch parameter'; } @@ -586,15 +589,15 @@ class BuildCommand extends Command { if (ensure && out != 'app') { if (_outputsAreFresh(arch)) { - print('${target.name} output already exists'); + print('${platform.name} output already exists'); return; } } final corePaths = await Build.buildCore( - target: target, + platform: platform, arch: arch, - mode: mode, + mode: coreMode, compatible: compatible, ); @@ -603,21 +606,21 @@ class BuildCommand extends Command { } if (out == 'helper') { - if (target != Target.windows) { + if (platform != TargetPlatform.windows) { throw '--out helper is only supported for windows'; } final coreHash = argResults?['core-hash'] as String?; if (coreHash == null || coreHash.isEmpty) { throw '--core-hash is required when --out=helper'; } - await Build.buildHelper(target, coreHash); + await Build.buildHelper(platform, coreHash); return; } if (out != 'app') { - if (target == Target.windows) { + if (platform == TargetPlatform.windows) { final token = await Build.calcSha256(corePaths.first); - await Build.buildHelper(target, token); + await Build.buildHelper(platform, token); } return; } @@ -625,16 +628,16 @@ class BuildCommand extends Command { final String desc = compatible ? '$archName-compatible' : (archName ?? ''); String appAssetSuffix = ''; - switch (target) { - case Target.windows: + switch (platform) { + case TargetPlatform.windows: appAssetSuffix = 'windows-$desc-setup.exe'; break; - case Target.macos: + case TargetPlatform.macos: appAssetSuffix = 'macos-$desc.dmg'; break; - case Target.linux: + case TargetPlatform.linux: break; - case Target.android: + case TargetPlatform.android: if (archName == 'universal') { appAssetSuffix = 'android-universal.apk'; } else if (arch == Arch.arm64) { @@ -647,14 +650,14 @@ class BuildCommand extends Command { break; } - switch (target) { - case Target.windows: - final token = target != Target.android + switch (platform) { + case TargetPlatform.windows: + final token = platform != TargetPlatform.android ? await Build.calcSha256(corePaths.first) : null; - Build.buildHelper(target, token!); + Build.buildHelper(platform, token!); _buildDistributor( - target: target, + platform: platform, targets: 'exe', args: ' --description $desc --build-dart-define=CORE_SHA256=$token', env: env, @@ -662,7 +665,7 @@ class BuildCommand extends Command { compatible: compatible, ); return; - case Target.linux: + case TargetPlatform.linux: final targetMap = {Arch.arm64: 'linux-arm64', Arch.amd64: 'linux-x64'}; final targets = [ 'deb', @@ -675,7 +678,7 @@ class BuildCommand extends Command { final ext = t == 'appimage' ? 'AppImage' : t; final currentSuffix = 'linux-$desc.$ext'; await _buildDistributor( - target: target, + platform: platform, targets: t, args: ' --description $desc --build-target-platform $defaultTarget', env: env, @@ -684,7 +687,7 @@ class BuildCommand extends Command { ); } return; - case Target.android: + case TargetPlatform.android: final targetMap = { Arch.arm: 'android-arm', Arch.arm64: 'android-arm64', @@ -701,7 +704,7 @@ class BuildCommand extends Command { : ',split-per-abi --build-target-platform ${defaultTargets.join(",")}'; _buildDistributor( - target: target, + platform: platform, targets: 'apk', args: buildArgs, env: env, @@ -709,7 +712,7 @@ class BuildCommand extends Command { compatible: compatible, ); return; - case Target.macos: + case TargetPlatform.macos: await _setMacOSImpeller(!compatible); await Build.exec( Build.getExecutable('rm -rf Pods Podfile.lock'), @@ -721,7 +724,7 @@ class BuildCommand extends Command { workingDirectory: 'macos', ); _buildDistributor( - target: target, + platform: platform, targets: 'dmg', args: ' --description $desc', env: env, @@ -735,9 +738,9 @@ class BuildCommand extends Command { Future main(Iterable 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.addCommand(BuildCommand(platform: TargetPlatform.android)); + runner.addCommand(BuildCommand(platform: TargetPlatform.linux)); + runner.addCommand(BuildCommand(platform: TargetPlatform.windows)); + runner.addCommand(BuildCommand(platform: TargetPlatform.macos)); runner.run(args); } From eb0e0f547d2144e65209199efeba7b84616cd7a4 Mon Sep 17 00:00:00 2001 From: VillagerTom Date: Sat, 4 Apr 2026 22:01:47 +0800 Subject: [PATCH 3/9] =?UTF-8?q?feat(build):=20=E4=B8=BA=20Linux=20?= =?UTF-8?q?=E6=B7=BB=E5=8A=A0=20--targets=20=E5=91=BD=E4=BB=A4=E8=A1=8C?= =?UTF-8?q?=E5=8F=82=E6=95=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 删除 setup.dart Linux 分支的 targets 硬编码,改为从 CI 输入 --- .github/workflows/build.yaml | 12 +++++++++++- setup.dart | 24 ++++++++++++++++++------ 2 files changed, 29 insertions(+), 7 deletions(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 4f6abfff5..64338c8a7 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -17,6 +17,9 @@ env: jobs: build: runs-on: ${{ matrix.os }} + defaults: + run: + shell: bash strategy: fail-fast: false matrix: @@ -205,7 +208,14 @@ jobs: run: | flutter pub get dart run build_runner build -d - dart setup.dart ${{ matrix.platform }} --arch ${{ matrix.arch }} ${{ env.IS_STABLE == 'true' && '--env stable' || '' }} ${{ matrix.compatible == true && '--compatible' || '' }} + dart setup.dart \ + ${{ matrix.platform }} \ + --arch ${{ matrix.arch }} \ + ${{ env.IS_STABLE == 'true' && '--env stable' || '' }} \ + ${{ matrix.compatible == true && '--compatible' || '' }} \ + ${{ matrix.platform == 'linux' && '--targets' || ''}} \ + ${{ matrix.platform == 'linux' && matrix.arch == 'amd64' && 'deb,rpm,appimage' || '' }} \ + ${{ matrix.platform == 'linux' && matrix.arch == 'arm64' && 'deb' || '' }} - name: Install SignPath (Windows) if: matrix.platform == 'windows' diff --git a/setup.dart b/setup.dart index 35c2ea05f..0f715fa89 100644 --- a/setup.dart +++ b/setup.dart @@ -339,6 +339,11 @@ class BuildCommand extends Command { ].join(','), help: 'The $name build desc', ); + argParser.addOption( + 'targets', + valueHelp: 'deb,zip,appimage,rpm', + help: 'The linux package formats (comma separated)', + ); } else { argParser.addOption( 'arch', @@ -666,15 +671,22 @@ class BuildCommand extends Command { ); return; case TargetPlatform.linux: + final validTargets = ['deb', 'rpm', 'appimage', 'zip']; + final targets = argResults?['targets'] ?? []; + if (targets.isEmpty) { + throw 'Invalid targets parameter'; + } + final invalidTargets = targets.where((t) => !validTargets.contains(t)).toList(); + if (invalidTargets.isNotEmpty) { + throw 'Invalid targets parameter: ${invalidTargets.join(', ')}'; + } + final targetMap = {Arch.arm64: 'linux-arm64', Arch.amd64: 'linux-x64'}; - final targets = [ - 'deb', - if (arch == Arch.amd64) 'appimage', - if (arch == Arch.amd64) 'rpm', - ]; final defaultTarget = targetMap[arch]; + await _setLinuxCoreSetuid(); - for (final t in targets) { + + for (final t in targets.isEmpty ? [''] : targets) { final ext = t == 'appimage' ? 'AppImage' : t; final currentSuffix = 'linux-$desc.$ext'; await _buildDistributor( From 421c73d350e30c591e1dbe25d264406b7035d6ac Mon Sep 17 00:00:00 2001 From: VillagerTom Date: Wed, 27 May 2026 00:57:21 +0800 Subject: [PATCH 4/9] =?UTF-8?q?feat(build):=20setup.dart=20=E6=B7=BB?= =?UTF-8?q?=E5=8A=A0=E6=9E=84=E5=BB=BA=E7=8E=AF=E5=A2=83=E6=A3=80=E6=9F=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - check linux and macos dependencies that were installed in the previous removed steps - check cargo and inno-setup for windows - specify and check ndk 27.0.12077973 for android build core, if env ANDROID_NDK is not set - prevent building app for unbuildable target platforms - prevent cross-builds for windows and linux target --- setup.dart | 159 ++++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 134 insertions(+), 25 deletions(-) diff --git a/setup.dart b/setup.dart index 0f715fa89..95547ba67 100644 --- a/setup.dart +++ b/setup.dart @@ -70,6 +70,76 @@ class BuildItem { } } +Future checkDeps({ + List? commands, + Map? devLibs, + Map? rtLibs, + List? files, + List? ndks, +}) async { + final missing = []; + + if (devLibs != null && devLibs.isNotEmpty) { + final pkgConfigExists = (await Process.run('which', ['pkg-config'])).exitCode == 0; + if (!pkgConfigExists) { + missing.add('pkg-config'); + } else { + for (final entry in devLibs.entries) { + final result = await Process.run('pkg-config', ['--exists', entry.value]); + if (result.exitCode != 0) missing.add(entry.key); + } + } + } + + if (rtLibs != null && rtLibs.isNotEmpty) { + for (final entry in rtLibs.entries) { + final result = await Process.run('sh', ['-c', 'ldconfig -p | grep ${entry.value}']); + if (result.exitCode != 0) missing.add(entry.key); + } + } + + if (ndks != null && ndks.isNotEmpty) { + final sdkmanager = join(Platform.environment['ANDROID_HOME']!, 'cmdline-tools', 'latest', 'bin', 'sdkmanager'); + final cmdlineToolsExist = File(sdkmanager).existsSync(); + if (!cmdlineToolsExist) { + missing.add('Android SDK Command-line Tools'); + } else { + for (final ndkVersion in ndks) { + final result = await Process.run(sdkmanager, ['--list_installed']); + final pattern = RegExp('^\\s.${RegExp.escape('ndk;$ndkVersion')}', multiLine: true); + final installed = pattern.hasMatch(result.stdout); + if (!installed) { + missing.add('Android NDK $ndkVersion'); + } + } + } + } + + if (commands != null && commands.isNotEmpty) { + for (final cmd in commands) { + final result = Platform.isWindows + ? await Process.run('where.exe', [cmd]) + : await Process.run('which', [cmd]); + if (result.exitCode != 0) { + missing.add(cmd); + } + } + } + + if (files != null && files.isNotEmpty) { + for (final filePath in files) { + if (!File(filePath).existsSync()) { + missing.add(basename(filePath)); + } + } + } + + if (missing.isNotEmpty) { + throw 'Missing required dependencies: ${missing.join(", ")}. ' + 'Please install them first. See README for details.'; + } +} + class Build { static bool isDev = false; @@ -115,26 +185,6 @@ class Build { static String get distPath => join(current, 'dist'); - static String _getCc(BuildItem buildItem) { - final environment = Platform.environment; - if (buildItem.platform == TargetPlatform.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 String getTags(BuildItem buildItem) { final baseTags = 'with_gvisor'; if (buildItem.platform == TargetPlatform.android && @@ -187,8 +237,7 @@ class Build { final isLib = mode == CoreMode.lib; final items = buildItems.where((element) { - return element.platform == platform && - (arch == null ? true : element.arch == arch); + return element.platform == platform && (arch == null || element.arch == arch); }).toList(); final List corePaths = []; @@ -220,14 +269,34 @@ class Build { } if (isLib) { env['CGO_ENABLED'] = '1'; - env['CC'] = _getCc(item); env['CFLAGS'] = '-O3 -Werror'; + if (item.platform == TargetPlatform.android) { + var ndkPath = Platform.environment['ANDROID_NDK']; + if (ndkPath == null) { + const ndkVersion = '28.2.13676358'; + final androidHome = Platform.environment['ANDROID_HOME']!; + await checkDeps(ndks: [ndkVersion]); + ndkPath = join(androidHome, 'ndk', ndkVersion); + } + final prebuiltDir = Directory(join(ndkPath, 'toolchains', 'llvm', 'prebuilt')); + 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', + }; + env['CC'] = join(prebuiltDir.listSync().first.path, 'bin', map[item.archName]); + } else { + env['CC'] = 'gcc'; + await checkDeps(commands: ['gcc']); + } } else { env['CGO_ENABLED'] = '0'; } final buildTags = getTags(item); + await checkDeps(commands: ['go']); await exec( ['go', 'mod', 'tidy'], name: 'go mod tidy', @@ -460,7 +529,7 @@ class BuildCommand extends Command { await Build.getDistributor(); await Build.exec( - name: name, + name: description, Build.getExecutable( 'flutter_distributor package --skip-clean --platform ${platform.name} --targets $targets --flutter-build-args=verbose$args$sentryArg$suffixArg --build-dart-define=APP_ENV=$env$appDevArg', ), @@ -622,6 +691,11 @@ class BuildCommand extends Command { return; } + if (out == 'app' && !platform.buildable) { + print('Platform incompatible, core built only!'); + return; + } + if (out != 'app') { if (platform == TargetPlatform.windows) { final token = await Build.calcSha256(corePaths.first); @@ -657,11 +731,20 @@ class BuildCommand extends Command { switch (platform) { case TargetPlatform.windows: + if (arch!.name != await systemArch) { + throw 'Corss-build to $name ${arch.name} target is not currently supported!'; + } + final token = platform != TargetPlatform.android ? await Build.calcSha256(corePaths.first) : null; + await checkDeps(commands: ['cargo']); Build.buildHelper(platform, token!); - _buildDistributor( + await checkDeps( + commands: ['rustup'], + files: [r'C:\Program Files (x86)\Inno Setup 6\ISCC.exe'], + ); + await _buildDistributor( platform: platform, targets: 'exe', args: ' --description $desc --build-dart-define=CORE_SHA256=$token', @@ -671,6 +754,10 @@ class BuildCommand extends Command { ); return; case TargetPlatform.linux: + if (arch!.name != await systemArch) { + throw 'Corss-build to $name ${arch.name} target is not currently supported!'; + } + final validTargets = ['deb', 'rpm', 'appimage', 'zip']; final targets = argResults?['targets'] ?? []; if (targets.isEmpty) { @@ -681,6 +768,25 @@ class BuildCommand extends Command { throw 'Invalid targets parameter: ${invalidTargets.join(', ')}'; } + final requiredCmds = ['clang', 'cmake', 'ninja', 'rustup']; + Map requiredRtLibs = {}; + if (targets.contains('deb')) requiredCmds.add('dpkg-deb'); + if (targets.contains('rpm')) requiredCmds.addAll(['rpm', 'patchelf']); + if (targets.contains('appimage')) { + requiredCmds.addAll(['appimagetool', 'locate']); + requiredRtLibs.addAll({'libfuse2': 'libfuse.so.2'}); + } + await checkDeps( + commands: requiredCmds, + devLibs: { + 'gtk3': 'gtk+-3.0', + 'libayatana-appindicator': 'ayatana-appindicator3-0.1', + 'keybinder-3.0': 'keybinder-3.0', + 'libcurl': 'libcurl', + }, + rtLibs: requiredRtLibs, + ); + final targetMap = {Arch.arm64: 'linux-arm64', Arch.amd64: 'linux-x64'}; final defaultTarget = targetMap[arch]; @@ -689,6 +795,7 @@ class BuildCommand extends Command { for (final t in targets.isEmpty ? [''] : targets) { final ext = t == 'appimage' ? 'AppImage' : t; final currentSuffix = 'linux-$desc.$ext'; + await _buildDistributor( platform: platform, targets: t, @@ -700,6 +807,7 @@ class BuildCommand extends Command { } return; case TargetPlatform.android: + await checkDeps(commands: ['rustup']); final targetMap = { Arch.arm: 'android-arm', Arch.arm64: 'android-arm64', @@ -725,6 +833,7 @@ class BuildCommand extends Command { ); return; case TargetPlatform.macos: + await checkDeps(commands: ['rustup', 'appdmg']); await _setMacOSImpeller(!compatible); await Build.exec( Build.getExecutable('rm -rf Pods Podfile.lock'), From 390cb7c50817abf9c1a48a8d9650a8a43ce13c72 Mon Sep 17 00:00:00 2001 From: VillagerTom Date: Sun, 3 May 2026 16:22:30 +0800 Subject: [PATCH 5/9] =?UTF-8?q?refactor(build):=20=E6=94=B9=E4=B8=BA?= =?UTF-8?q?=E4=BD=BF=E7=94=A8=20Arch.same=20=E5=9B=9E=E8=90=BD=20host=20ar?= =?UTF-8?q?ch,=20=E7=9C=81=E7=95=A5=20--arch=20auto;=20=E8=A1=A5=E5=85=85?= =?UTF-8?q?=E7=BC=BA=E5=A4=B1=E7=9A=84=20await?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Replace --arch auto flag with implicit fallback via ArchExt.same - Remove systemArch/_mapHostArch (superseded by ArchExt.same) - Keep _expectedOutputs etc. for --ensure support - Add missing awaits for buildHelper and _buildDistributor calls - Consolidate .vscode/ tasks and launch configs into single auto-detecting entries --- .vscode/launch.json | 55 +------------------ .vscode/tasks.json | 97 +++------------------------------- setup.dart | 126 +++++++++++++++++++++++--------------------- 3 files changed, 75 insertions(+), 203 deletions(-) diff --git a/.vscode/launch.json b/.vscode/launch.json index 83252d98c..7168334ae 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -2,67 +2,16 @@ "version": "0.2.0", "configurations": [ { - "name": "Bettbox Windows Dev", + "name": "Bettbox Dev", "request": "launch", "type": "dart", "flutterMode": "debug", "program": "lib/main.dart", - "deviceId": "windows", "toolArgs": [ "--dart-define", "APP_DEV=true" ], - "preLaunchTask": "bettbox: ensure windows dev core" - }, - { - "name": "Bettbox macOS Dev", - "request": "launch", - "type": "dart", - "flutterMode": "debug", - "program": "lib/main.dart", - "deviceId": "macos", - "toolArgs": [ - "--dart-define", - "APP_DEV=true" - ], - "preLaunchTask": "bettbox: ensure macos dev core" - }, - { - "name": "Bettbox Linux Dev", - "request": "launch", - "type": "dart", - "flutterMode": "debug", - "program": "lib/main.dart", - "deviceId": "linux", - "toolArgs": [ - "--dart-define", - "APP_DEV=true" - ], - "preLaunchTask": "bettbox: ensure linux dev core" - }, - { - "name": "Bettbox Android x64 Dev", - "request": "launch", - "type": "dart", - "flutterMode": "debug", - "program": "lib/main.dart", - "toolArgs": [ - "--dart-define", - "APP_DEV=true" - ], - "preLaunchTask": "bettbox: ensure android x64 core" - }, - { - "name": "Bettbox Android arm64 Dev", - "request": "launch", - "type": "dart", - "flutterMode": "debug", - "program": "lib/main.dart", - "toolArgs": [ - "--dart-define", - "APP_DEV=true" - ], - "preLaunchTask": "bettbox: ensure android arm64 core" + "preLaunchTask": "bettbox: ensure dev core" } ] } diff --git a/.vscode/tasks.json b/.vscode/tasks.json index 6e0a15a24..1b22109e4 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -2,102 +2,17 @@ "version": "2.0.0", "tasks": [ { - "label": "bettbox: ensure windows dev core", - "type": "shell", - "command": "dart", + "label": "bettbox: ensure dev core", + "type": "process", + "command": "bash", "args": [ - "./setup.dart", - "windows", - "--arch", - "auto", - "--out", - "core", - "--dev", - "--ensure" + "-c", + "exec dart ./setup.dart \"$(flutter devices --machine 2>/dev/null | jq -r --arg id \"${command:flutter.getSelectedDeviceId}\" '.[] | select(.id == $id).targetPlatform' | sed 's/-.*//;s/darwin/macos/')\" --out core --dev --ensure" ], "options": { "cwd": "${workspaceFolder}" }, - "problemMatcher": [], - "group": "build" - }, - { - "label": "bettbox: ensure macos dev core", - "type": "shell", - "command": "dart", - "args": [ - "./setup.dart", - "macos", - "--arch", - "auto", - "--out", - "core", - "--dev", - "--ensure" - ], - "options": { - "cwd": "${workspaceFolder}" - }, - "problemMatcher": [], - "group": "build" - }, - { - "label": "bettbox: ensure linux dev core", - "type": "shell", - "command": "dart", - "args": [ - "./setup.dart", - "linux", - "--arch", - "auto", - "--out", - "core", - "--dev", - "--ensure" - ], - "options": { - "cwd": "${workspaceFolder}" - }, - "problemMatcher": [], - "group": "build" - }, - { - "label": "bettbox: ensure android x64 core", - "type": "shell", - "command": "dart", - "args": [ - "./setup.dart", - "android", - "--arch", - "amd64", - "--out", - "core", - "--ensure" - ], - "options": { - "cwd": "${workspaceFolder}" - }, - "problemMatcher": [], - "group": "build" - }, - { - "label": "bettbox: ensure android arm64 core", - "type": "shell", - "command": "dart", - "args": [ - "./setup.dart", - "android", - "--arch", - "arm64", - "--out", - "core", - "--ensure" - ], - "options": { - "cwd": "${workspaceFolder}" - }, - "problemMatcher": [], - "group": "build" + "problemMatcher": [] } ] } diff --git a/setup.dart b/setup.dart index 95547ba67..55bf23bd3 100644 --- a/setup.dart +++ b/setup.dart @@ -57,12 +57,58 @@ enum CoreMode { core, lib } enum Arch { amd64, arm64, arm } +extension ArchExt on Arch { + Map get archMap { + switch (Platform.operatingSystem) { + case 'windows': + return { + 'AMD64': 'amd64', + 'x86': 'amd32', + 'ARM64': 'arm64', + 'ARM': 'arm' + }; + case 'linux' || 'android': + return { + 'x86_64': 'amd64', + 'i386': 'amd32', + 'i486': 'amd32', + 'i586': 'amd32', + 'i686': 'amd32', + 'aarch64': 'arm64', + 'armv5l': 'arm', + 'armv6l': 'arm', + 'armv7l': 'arm' + }; + case 'macos': + return { + 'x86_64': 'amd64', + 'arm64': 'arm64', + 'arm64e': 'arm64' + }; + default: + throw 'Unsupported platform!'; + } + } + + bool get same { + final String hostArchName; + if (Platform.isWindows) { + hostArchName = Platform.environment['PROCESSOR_ARCHITECTURE']!; + } else { + var info = Process.runSync('uname', ['-m']); + hostArchName = info.stdout.toString().trim(); + } + final hostArch = archMap[hostArchName] ?? hostArchName; + return name == hostArch ? true : false; + } +} + class BuildItem { TargetPlatform platform; - Arch? arch; + Arch arch; String? archName; - BuildItem({required this.platform, this.arch, this.archName}); + BuildItem({required this.platform, required this.arch, this.archName}); @override String toString() { @@ -258,9 +304,7 @@ class Build { final Map env = {}; env['GOOS'] = item.platform.os; - if (item.arch != null) { - env['GOARCH'] = item.arch!.name; - } + env['GOARCH'] = item.arch.name; if (item.arch == Arch.amd64 && (item.platform == TargetPlatform.windows || item.platform == TargetPlatform.linux || @@ -397,15 +441,13 @@ class Build { class BuildCommand extends Command { TargetPlatform platform; + //TODO: Delete arg option 'targets' for android BuildCommand({required this.platform}) { if (platform == TargetPlatform.android || platform == TargetPlatform.linux) { argParser.addOption( 'arch', - valueHelp: [ - if (platform != TargetPlatform.android) 'auto', - ...arches.map((e) => e.name), - ].join(','), + valueHelp: arches.map((e) => e.name).join(','), help: 'The $name build desc', ); argParser.addOption( @@ -416,7 +458,7 @@ class BuildCommand extends Command { } else { argParser.addOption( 'arch', - valueHelp: ['auto', ...arches.map((e) => e.name)].join(','), + valueHelp: arches.map((e) => e.name).join(','), help: 'The $name build archName', ); } @@ -458,8 +500,8 @@ class BuildCommand extends Command { String get name => platform.name; List get arches => Build.buildItems - .where((element) => element.platform == platform && element.arch != null) - .map((e) => e.arch!) + .where((element) => element.platform == platform) + .map((e) => e.arch) .toList(); Future _setLinuxCoreSetuid() async { @@ -537,25 +579,6 @@ class BuildCommand extends Command { ); } - Future 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; - } - - String? _mapHostArch(String? hostArch) { - if (hostArch == null) return null; - final lower = hostArch.toLowerCase(); - if (lower == 'amd64' || lower == 'x86_64' || lower == 'x64') return 'amd64'; - if (lower == 'arm64' || lower == 'aarch64') return 'arm64'; - if (lower.startsWith('arm')) return 'arm'; - return null; - } - List _expectedOutputs(Arch? arch) { final items = Build.buildItems.where((element) { return element.platform == platform && @@ -632,30 +655,13 @@ class BuildCommand extends Command { Future run() async { final coreMode = platform == TargetPlatform.android ? CoreMode.lib : CoreMode.core; final String out = argResults?['out'] ?? (platform.buildable ? 'app' : 'core'); - final env = argResults?['env'] ?? 'pre'; + final String? archName = argResults?['arch']; + final String env = argResults?['env'] ?? 'pre'; Build.isDev = argResults?['dev'] ?? false; + Arch? arch = arches.where((element) => element.name == archName).firstOrNull; - String? archName = argResults?['arch']; - if (archName == 'auto') { - if (platform == TargetPlatform.android) { - throw '--arch auto is not supported for android; choose the device ABI explicitly'; - } - if (!platform.same) { - throw '--arch auto can only be used for the current host platform'; - } - archName = _mapHostArch(await systemArch); - if (archName == null) { - throw 'Unable to detect host architecture'; - } - } - - final currentArches = arches - .where((element) => element.name == archName) - .toList(); - final arch = currentArches.isEmpty ? null : currentArches.first; - - if (arch == null && platform != TargetPlatform.android) { - throw 'Invalid arch parameter'; + if (platform != TargetPlatform.android) { + arch ??= arches.where((element) => element.same).first; } final bool compatible = argResults?['compatible'] ?? false; @@ -704,7 +710,9 @@ class BuildCommand extends Command { return; } - final String desc = compatible ? '$archName-compatible' : (archName ?? ''); + final String desc = platform == TargetPlatform.android + ? '' + : '${archName ?? arch!.name}${compatible ? "-compatible" : ""}'; String appAssetSuffix = ''; switch (platform) { @@ -731,7 +739,7 @@ class BuildCommand extends Command { switch (platform) { case TargetPlatform.windows: - if (arch!.name != await systemArch) { + if (!arch!.same) { throw 'Corss-build to $name ${arch.name} target is not currently supported!'; } @@ -739,7 +747,7 @@ class BuildCommand extends Command { ? await Build.calcSha256(corePaths.first) : null; await checkDeps(commands: ['cargo']); - Build.buildHelper(platform, token!); + await Build.buildHelper(platform, token!); await checkDeps( commands: ['rustup'], files: [r'C:\Program Files (x86)\Inno Setup 6\ISCC.exe'], @@ -754,7 +762,7 @@ class BuildCommand extends Command { ); return; case TargetPlatform.linux: - if (arch!.name != await systemArch) { + if (!arch!.same) { throw 'Corss-build to $name ${arch.name} target is not currently supported!'; } @@ -823,7 +831,7 @@ class BuildCommand extends Command { ? ' --build-target-platform ${defaultTargets.join(",")} --description universal' : ',split-per-abi --build-target-platform ${defaultTargets.join(",")}'; - _buildDistributor( + await _buildDistributor( platform: platform, targets: 'apk', args: buildArgs, @@ -844,7 +852,7 @@ class BuildCommand extends Command { Build.getExecutable('pod install --repo-update'), workingDirectory: 'macos', ); - _buildDistributor( + await _buildDistributor( platform: platform, targets: 'dmg', args: ' --description $desc', From 199c4178bbec00f1e2cf375524678fc9ad4c31e8 Mon Sep 17 00:00:00 2001 From: VillagerTom Date: Mon, 29 Jun 2026 01:15:42 +0800 Subject: [PATCH 6/9] =?UTF-8?q?fix(build):=20=E5=9C=A8=E8=B0=83=E7=94=A8?= =?UTF-8?q?=20buildCore()=20=E4=B9=8B=E5=89=8D=E8=B5=8B=E5=80=BC=20arch=20?= =?UTF-8?q?=E5=8F=98=E9=87=8F=EF=BC=9B=E6=8B=A6=E6=88=AA=E6=97=A0=E6=95=88?= =?UTF-8?q?=20arch=20=E5=8F=82=E6=95=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- setup.dart | 25 +++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/setup.dart b/setup.dart index 55bf23bd3..01ad35aad 100644 --- a/setup.dart +++ b/setup.dart @@ -654,14 +654,23 @@ class BuildCommand extends Command { @override Future run() async { final coreMode = platform == TargetPlatform.android ? CoreMode.lib : CoreMode.core; - final String out = argResults?['out'] ?? (platform.buildable ? 'app' : 'core'); - final String? archName = argResults?['arch']; + final String out = argResults?['out'] ?? (platform.same ? 'app' : 'core'); + final String? archParam = argResults?['arch']; final String env = argResults?['env'] ?? 'pre'; Build.isDev = argResults?['dev'] ?? false; - Arch? arch = arches.where((element) => element.name == archName).firstOrNull; - if (platform != TargetPlatform.android) { - arch ??= arches.where((element) => element.same).first; + Arch? arch; + if (archParam == null) { + if (platform != TargetPlatform.android) { + arch = arches.firstWhere((element) => element.same); + } + } else if (archParam == 'universal') { + if (platform != TargetPlatform.android && platform != TargetPlatform.macos) { + throw 'Invalid arch parameter!'; + } + } else { + arch = arches.where((element) => element.name == archParam).firstOrNull; + if (arch == null) throw 'Invalid arch parameter!'; } final bool compatible = argResults?['compatible'] ?? false; @@ -712,7 +721,7 @@ class BuildCommand extends Command { final String desc = platform == TargetPlatform.android ? '' - : '${archName ?? arch!.name}${compatible ? "-compatible" : ""}'; + : '${archParam ?? arch!.name}${compatible ? "-compatible" : ""}'; String appAssetSuffix = ''; switch (platform) { @@ -725,7 +734,7 @@ class BuildCommand extends Command { case TargetPlatform.linux: break; case TargetPlatform.android: - if (archName == 'universal') { + if (archParam == 'universal') { appAssetSuffix = 'android-universal.apk'; } else if (arch == Arch.arm64) { appAssetSuffix = 'android-arm64-v8a.apk'; @@ -827,7 +836,7 @@ class BuildCommand extends Command { .map((e) => targetMap[e]) .toList(); - final buildArgs = archName == 'universal' + final buildArgs = archParam == 'universal' ? ' --build-target-platform ${defaultTargets.join(",")} --description universal' : ',split-per-abi --build-target-platform ${defaultTargets.join(",")}'; From 592986b916f0a9e2f7932b9f579ef797490125b7 Mon Sep 17 00:00:00 2001 From: VillagerTom Date: Sat, 9 May 2026 01:03:56 +0800 Subject: [PATCH 7/9] =?UTF-8?q?fix(build):=20=E4=BF=AE=E6=AD=A3=E5=91=BD?= =?UTF-8?q?=E4=BB=A4=E9=80=89=E9=A1=B9=E5=8F=8A=E5=85=B6=20help?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - valueHelp 改为 allowed,拦截无效命令行参数 - 添加 abbr,支持短格式参数 - 修复错误的判断条件和说明 --- Makefile | 57 ++++++++++++++++++++++++++++++++++++++++++------ setup.dart | 64 ++++++++++++++++++++++++++++-------------------------- 2 files changed, 83 insertions(+), 38 deletions(-) diff --git a/Makefile b/Makefile index 627966f05..84f14a680 100644 --- a/Makefile +++ b/Makefile @@ -1,10 +1,53 @@ -android_arm64: +android_app_arm64: dart ./setup.dart android --arch arm64 -macos_arm64: - dart ./setup.dart macos --arch arm64 -android_app: + +android_app_all: dart ./setup.dart android -android_arm64_core: + +android_app_universal: + dart ./setup.dart android --arch universal + +android_core_arm64: dart ./setup.dart android --arch arm64 --out core -macos_arm64_core: - dart ./setup.dart macos --arch arm64 --out core \ No newline at end of file + +android_core_all: + dart ./setup.dart android --out core + +macos_app: + dart ./setup.dart macos + +macos_app_arm64: + dart ./setup.dart macos --arch arm64 + +macos_app_amd64: + dart ./setup.dart macos --arch amd64 + +macos_app_universal: + dart ./setup.dart macos --arch universal + +macos_core: + dart ./setup.dart macos --out core + +macos_core_arm64: + dart ./setup.dart macos --arch arm64 --out core + +macos_core_amd64: + dart ./setup.dart macos --arch amd64 --out core + +windows_app: + dart ./setup.dart windows + +windows_app_amd64_compatible: + dart ./setup.dart windows --arch amd64 --compatible + +linux: + dart ./setup.dart linux --build-only + +linux_amd64_compatible: + dart ./setup.dart linux --arch amd64 --compatible --build-only + +linux_core: + dart ./setup.dart linux --out core + +linux_core_amd64_compatible: + dart ./setup.dart linux --out core --arch amd64 --compatible diff --git a/setup.dart b/setup.dart index 01ad35aad..156704ce2 100644 --- a/setup.dart +++ b/setup.dart @@ -441,36 +441,35 @@ class Build { class BuildCommand extends Command { TargetPlatform platform; - //TODO: Delete arg option 'targets' for android BuildCommand({required this.platform}) { - if (platform == TargetPlatform.android || - platform == TargetPlatform.linux) { - argParser.addOption( - 'arch', - valueHelp: arches.map((e) => e.name).join(','), - help: 'The $name build desc', - ); - argParser.addOption( + argParser.addOption( + 'arch', + abbr: 'a', + allowed: platform == TargetPlatform.android || platform == TargetPlatform.macos + ? [...arches.map((e) => e.name), 'universal'] + : arches.map((e) => e.name), + help: 'The architecture of $name build; omit this to select ' + '${platform != TargetPlatform.android ? "the host" : "every"} architecture', + ); + if (platform == TargetPlatform.linux) { + argParser.addMultiOption( 'targets', - valueHelp: 'deb,zip,appimage,rpm', - help: 'The linux package formats (comma separated)', - ); - } else { - argParser.addOption( - 'arch', - valueHelp: arches.map((e) => e.name).join(','), - help: 'The $name build archName', + abbr: 't', + allowed: ['deb', 'rpm', 'appimage', 'zip'], + help: 'Linux package formats (multiple selections, seperated by ",")', ); } argParser.addOption( 'out', - valueHelp: [ + abbr: 'o', + allowed: [ if (platform.buildable) 'app', 'core', 'core-only', 'helper', - ].join(','), - help: 'The $name build arch', + ], + help: 'Build the full app or only the core', + defaultsTo: platform.buildable ? 'app' : 'core', ); argParser.addOption( 'core-hash', @@ -479,12 +478,15 @@ class BuildCommand extends Command { ); argParser.addOption( 'env', - valueHelp: ['pre', 'stable'].join(','), - help: 'The $name build env', + abbr: 'e', + allowed: ['pre', 'stable'], + help: 'The value of dart-define APP_ENV, used to identify the release channel', + defaultsTo: 'pre', ); argParser.addFlag( 'compatible', - help: 'Build with GOAMD64=v2 for broader compatibility on amd64', + abbr: 'C', + help: 'Build with GOAMD64=v1 for broader compatibility on amd64', ); argParser.addFlag('dev', help: 'Build debug/dev variant'); argParser.addFlag( @@ -550,7 +552,7 @@ class BuildCommand extends Command { required TargetPlatform platform, required String targets, String args = '', - required String env, + required String appEnv, required String suffix, bool compatible = false, }) async { @@ -573,7 +575,7 @@ class BuildCommand extends Command { await Build.exec( name: description, Build.getExecutable( - 'flutter_distributor package --skip-clean --platform ${platform.name} --targets $targets --flutter-build-args=verbose$args$sentryArg$suffixArg --build-dart-define=APP_ENV=$env$appDevArg', + 'flutter_distributor package --skip-clean --platform ${platform.name} --targets $targets --flutter-build-args=verbose$args$sentryArg$suffixArg --build-dart-define=APP_ENV=$appEnv$appDevArg', ), environment: environment, ); @@ -654,9 +656,9 @@ class BuildCommand extends Command { @override Future run() async { final coreMode = platform == TargetPlatform.android ? CoreMode.lib : CoreMode.core; - final String out = argResults?['out'] ?? (platform.same ? 'app' : 'core'); + final String out = argResults?['out'] ?? (platform.buildable ? 'app' : 'core'); final String? archParam = argResults?['arch']; - final String env = argResults?['env'] ?? 'pre'; + final String appEnv = argResults?['env'] ?? 'pre'; Build.isDev = argResults?['dev'] ?? false; Arch? arch; @@ -765,7 +767,7 @@ class BuildCommand extends Command { platform: platform, targets: 'exe', args: ' --description $desc --build-dart-define=CORE_SHA256=$token', - env: env, + appEnv: appEnv, suffix: appAssetSuffix, compatible: compatible, ); @@ -817,7 +819,7 @@ class BuildCommand extends Command { platform: platform, targets: t, args: ' --description $desc --build-target-platform $defaultTarget', - env: env, + appEnv: appEnv, suffix: currentSuffix, compatible: compatible, ); @@ -844,7 +846,7 @@ class BuildCommand extends Command { platform: platform, targets: 'apk', args: buildArgs, - env: env, + appEnv: appEnv, suffix: appAssetSuffix, compatible: compatible, ); @@ -865,7 +867,7 @@ class BuildCommand extends Command { platform: platform, targets: 'dmg', args: ' --description $desc', - env: env, + appEnv: appEnv, suffix: appAssetSuffix, compatible: compatible, ); From 7a4a7a6bb2752adaca9b90d22c65a487a9e0894b Mon Sep 17 00:00:00 2001 From: VillagerTom Date: Fri, 29 May 2026 16:33:40 +0800 Subject: [PATCH 8/9] =?UTF-8?q?feat(build):=20=E6=B7=BB=E5=8A=A0=20--build?= =?UTF-8?q?-only=20=E5=91=BD=E4=BB=A4=E8=A1=8C=E5=8F=82=E6=95=B0=EF=BC=8C?= =?UTF-8?q?=E7=94=A8=E4=BA=8E=E8=B7=B3=E8=BF=87=E6=89=93=E5=8C=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- setup.dart | 75 +++++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 55 insertions(+), 20 deletions(-) diff --git a/setup.dart b/setup.dart index 156704ce2..1522bae20 100644 --- a/setup.dart +++ b/setup.dart @@ -493,6 +493,11 @@ class BuildCommand extends Command { 'ensure', help: 'Skip build if output artifact already exists', ); + argParser.addFlag( + 'build-only', + abbr: 'B', + help: 'Skip packaging the app with flutter_distributor' + ); } @override @@ -555,27 +560,41 @@ class BuildCommand extends Command { required String appEnv, required String suffix, bool compatible = false, + final bool buildOnly = false, }) async { final sentryDsn = Platform.environment['SENTRY_DSN'] ?? ''; - final sentryArg = sentryDsn.isNotEmpty - ? ' --build-dart-define=SENTRY_DSN=$sentryDsn' - : ''; - final suffixArg = suffix.isNotEmpty - ? ' --build-dart-define=APP_ASSET_SUFFIX=$suffix' - : ''; + String sentryArg = ''; + if (sentryDsn.isNotEmpty) { + sentryArg = buildOnly + ? ' --dart-define=SENTRY_DSN=$sentryDsn' + : ' --build-dart-define=SENTRY_DSN=$sentryDsn'; + } + String suffixArg = ''; + if (suffix.isNotEmpty) { + suffixArg = buildOnly + ? ' --dart-define=APP_ASSET_SUFFIX=$suffix' + : ' --build-dart-define=APP_ASSET_SUFFIX=$suffix'; + } - final appDevArg = Build.isDev ? ' --build-dart-define=APP_DEV=true' : ''; + String appDevArg = ''; + if (Build.isDev) { + appDevArg = buildOnly + ? ' --dart-define=APP_DEV=true' + : ' --build-dart-define=APP_DEV=true'; + } final environment = Map.from(Platform.environment); if (compatible) { environment['BETTBOX_COMPATIBLE_BUILD'] = '1'; } - await Build.getDistributor(); + if (!buildOnly) await Build.getDistributor(); await Build.exec( name: description, Build.getExecutable( - 'flutter_distributor package --skip-clean --platform ${platform.name} --targets $targets --flutter-build-args=verbose$args$sentryArg$suffixArg --build-dart-define=APP_ENV=$appEnv$appDevArg', + buildOnly + ? 'flutter build ${platform == TargetPlatform.android ? "apk" : platform.name} --release --verbose$args$sentryArg$suffixArg --dart-define=APP_ENV=$appEnv$appDevArg' + : 'flutter_distributor package --skip-clean --platform ${platform.name} --targets $targets --flutter-build-args=verbose$args$sentryArg --build-dart-define=APP_ENV=$appEnv$appDevArg', ), environment: environment, ); @@ -677,6 +696,12 @@ class BuildCommand extends Command { final bool compatible = argResults?['compatible'] ?? false; final bool ensure = argResults?['ensure'] ?? false; + bool buildOnly = argResults?['build-only'] ?? false; + + if (platform == TargetPlatform.android && buildOnly) { + print('Warning: Ignored --build-only.'); + buildOnly = false; + } if (ensure && out != 'app') { if (_outputsAreFresh(arch)) { @@ -761,15 +786,18 @@ class BuildCommand extends Command { await Build.buildHelper(platform, token!); await checkDeps( commands: ['rustup'], - files: [r'C:\Program Files (x86)\Inno Setup 6\ISCC.exe'], + files: buildOnly ? null : [r'C:\Program Files (x86)\Inno Setup 6\ISCC.exe'], ); await _buildDistributor( platform: platform, targets: 'exe', - args: ' --description $desc --build-dart-define=CORE_SHA256=$token', + args: buildOnly + ? ' --dart-define=CORE_SHA256=$token' + : ' --description $desc --build-dart-define=CORE_SHA256=$token', appEnv: appEnv, suffix: appAssetSuffix, compatible: compatible, + buildOnly: buildOnly, ); return; case TargetPlatform.linux: @@ -779,12 +807,14 @@ class BuildCommand extends Command { final validTargets = ['deb', 'rpm', 'appimage', 'zip']; final targets = argResults?['targets'] ?? []; - if (targets.isEmpty) { - throw 'Invalid targets parameter'; - } - final invalidTargets = targets.where((t) => !validTargets.contains(t)).toList(); - if (invalidTargets.isNotEmpty) { - throw 'Invalid targets parameter: ${invalidTargets.join(', ')}'; + if (!buildOnly) { + if (targets.isEmpty) { + throw 'Invalid targets parameter'; + } + final invalidTargets = targets.where((t) => !validTargets.contains(t)).toList(); + if (invalidTargets.isNotEmpty) { + throw 'Invalid targets parameter: ${invalidTargets.join(', ')}'; + } } final requiredCmds = ['clang', 'cmake', 'ninja', 'rustup']; @@ -818,10 +848,13 @@ class BuildCommand extends Command { await _buildDistributor( platform: platform, targets: t, - args: ' --description $desc --build-target-platform $defaultTarget', + args: buildOnly + ? ' --target-platform $defaultTarget' + : ' --description $desc --build-target-platform $defaultTarget', appEnv: appEnv, suffix: currentSuffix, compatible: compatible, + buildOnly: buildOnly, ); } return; @@ -852,7 +885,8 @@ class BuildCommand extends Command { ); return; case TargetPlatform.macos: - await checkDeps(commands: ['rustup', 'appdmg']); + await checkDeps(commands: ['rustup']); + if (!buildOnly) await checkDeps(commands: ['appdmg']); await _setMacOSImpeller(!compatible); await Build.exec( Build.getExecutable('rm -rf Pods Podfile.lock'), @@ -866,10 +900,11 @@ class BuildCommand extends Command { await _buildDistributor( platform: platform, targets: 'dmg', - args: ' --description $desc', + args: buildOnly ? '' : ' --description $desc', appEnv: appEnv, suffix: appAssetSuffix, compatible: compatible, + buildOnly: buildOnly, ); return; } From 680827e360aef0403bbc0773165b7aede203ac4e Mon Sep 17 00:00:00 2001 From: VillagerTom Date: Wed, 12 Aug 2026 00:25:40 +0800 Subject: [PATCH 9/9] =?UTF-8?q?feat(debug):=20Windows=20host=20=E4=BD=BF?= =?UTF-8?q?=E7=94=A8=E7=BA=AF=20powershell=20=E5=91=BD=E4=BB=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .vscode/tasks.json | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/.vscode/tasks.json b/.vscode/tasks.json index 1b22109e4..40e910946 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -12,7 +12,22 @@ "options": { "cwd": "${workspaceFolder}" }, - "problemMatcher": [] + "problemMatcher": [], + "windows": { + "command": "powershell.exe", + "args": [ + "-NoProfile", + "-ExecutionPolicy", + "Bypass", + "-Command", + "$id = \"${command:flutter.getSelectedDeviceId}\";", + "$d = (flutter devices --machine 2>$null | ConvertFrom-Json) | Where-Object { $_.id -eq $id } | Select-Object -First 1;", + "if (-not $d) { Write-Error \"No device selected: $id\"; exit 1 };", + "$p = $d.targetPlatform.Split('-')[0];", + "if ($p -eq 'darwin') { $p = 'macos' };", + "& dart ./setup.dart $p --out=core --dev --ensure" + ] + } } ] -} +} \ No newline at end of file