/// Runs the Build workspace script. library; import 'dart:io'; /// Builds platform-specific Flutter desktop release artifacts. Future main(List arguments) async { final platform = _option(arguments, '--platform') ?? 'current'; final outputDirectory = Directory(_option(arguments, '--output') ?? 'build/releases'); await outputDirectory.create(recursive: true); final resolved = platform == 'current' ? _currentPlatform() : platform; switch (resolved) { case 'macos': await _buildMacOs(outputDirectory); break; case 'windows': await _buildWindows(outputDirectory); break; case 'linux': await _buildLinux(outputDirectory); break; default: throw ArgumentError.value( platform, '--platform', 'Expected current, macos, windows, or linux.', ); } } Future _buildMacOs(Directory outputDirectory) async { await _run('flutter', ['build', 'macos', '--release']); final releaseDir = Directory('build/macos/Build/Products/Release'); final apps = releaseDir .listSync() .whereType() .where((entry) => entry.path.endsWith('.app')) .toList(growable: false); if (apps.isEmpty) { throw StateError('No macOS .app found in ${releaseDir.path}.'); } for (final app in apps) { await _copyDirectory( app, Directory(_join(outputDirectory.path, _basename(app.path)))); } } Future _buildWindows(Directory outputDirectory) async { await _run('flutter', ['build', 'windows', '--release']); final bundle = Directory('build/windows/x64/runner/Release'); if (!await bundle.exists()) { throw StateError('No Windows release bundle found in ${bundle.path}.'); } await _copyDirectory( bundle, Directory(_join(outputDirectory.path, 'windows'))); await _run('dart', ['run', 'msix:create']); final msixFiles = Directory.current .listSync(recursive: true) .whereType() .where((file) => file.path.endsWith('.msix')) .toList(growable: false); if (msixFiles.isEmpty) { throw StateError('No .msix artifact produced by dart run msix:create.'); } for (final file in msixFiles) { await file.copy(_join(outputDirectory.path, _basename(file.path))); } } Future _buildLinux(Directory outputDirectory) async { await _run('flutter', ['build', 'linux', '--release']); await _run('appimage-builder', [ '--recipe', 'AppImageBuilder.yml', '--skip-test', ]); final appImages = Directory.current .listSync(recursive: true) .whereType() .where((file) => file.path.endsWith('.AppImage')) .toList(growable: false); if (appImages.isEmpty) { throw StateError('No AppImage artifact produced by appimage-builder.'); } for (final file in appImages) { await file.copy(_join(outputDirectory.path, _basename(file.path))); } } Future _run(String executable, List arguments) async { stdout.writeln('> $executable ${arguments.join(' ')}'); final result = await Process.run( executable, arguments, runInShell: Platform.isWindows, ); stdout.write(result.stdout); stderr.write(result.stderr); if (result.exitCode != 0) { throw ProcessException( executable, arguments, 'Command failed with exit code ${result.exitCode}.', result.exitCode, ); } } Future _copyDirectory(Directory source, Directory destination) async { await destination.create(recursive: true); await for (final entity in source.list(recursive: false)) { final targetPath = _join(destination.path, _basename(entity.path)); if (entity is Directory) { await _copyDirectory(entity, Directory(targetPath)); } else if (entity is File) { await entity.copy(targetPath); } } } String _currentPlatform() { if (Platform.isMacOS) return 'macos'; if (Platform.isWindows) return 'windows'; if (Platform.isLinux) return 'linux'; throw UnsupportedError('No Flutter desktop build target for this platform.'); } String _basename(String path) { final normalized = path.replaceAll('\\', '/'); return normalized.substring(normalized.lastIndexOf('/') + 1); } String _join(String first, String second) { final separator = Platform.pathSeparator; final left = first.endsWith(separator) ? first.substring(0, first.length - separator.length) : first; final right = second.startsWith(separator) ? second.substring(1) : second; return '$left$separator$right'; } String? _option(List arguments, String name) { final index = arguments.indexOf(name); if (index == -1 || index + 1 >= arguments.length) { return null; } return arguments[index + 1]; }