From 2fc76c7896045582391dafae674397b8c3b78b2a Mon Sep 17 00:00:00 2001 From: Pierre-Louis <6655696+guidezpl@users.noreply.github.com> Date: Sat, 16 Mar 2024 09:13:56 +0100 Subject: [PATCH] Reland #128236 "Improve build output for all platforms" (#143166) Reland #128236, reverted in https://github.com/flutter/flutter/pull/143125. This PR contains [one additional commit](https://github.com/flutter/flutter/pull/143166/commits/199baea9a99bc905e72d2b069259498dd8f7defd), fixing the 2 failed tests. ## Original description Improves the build output: 1. Gives confirmation that the build succeeded, in green 1. Gives the path to the built executable, without a trailing period to make it slightly easier to cmd/ctrl+open 1. Gives the size of the built executable (when the built executable is self contained) ### `apk`, `appbundle` image image ### `macos`, `ios`, `ipa` Build executables are self-contained and use a newly introduced `OperatingSystemUtils.getDirectorySize`. image image image ### `windows`, `linux`, and `web` Build executables aren't self-contained, and folder size can sometimes overestimate distribution size, therefore their size isn't mentioned (see discussion below). image image image ### Size reporting When applicable, the printed size matches the OS reported size. - macOS image - Windows image - Linux image ## Related issues Part of #120127 Fixes https://github.com/flutter/flutter/issues/121401 --- dev/devicelab/lib/tasks/run_tests.dart | 2 +- .../flutter_tools/lib/src/android/gradle.dart | 13 ++++---- packages/flutter_tools/lib/src/base/os.dart | 12 ++++++++ .../flutter_tools/lib/src/base/utils.dart | 13 ++++++-- .../lib/src/commands/build_ios.dart | 27 +++++++++++++++-- .../lib/src/isolated/resident_web_runner.dart | 2 +- .../lib/src/linux/build_linux.dart | 22 ++++++++++++-- .../lib/src/macos/build_macos.dart | 18 +++++++++++ .../lib/src/resident_runner.dart | 2 +- .../flutter_tools/lib/src/web/compile.dart | 9 ++++++ .../lib/src/windows/build_windows.dart | 21 +++++++------ .../hermetic/build_ios_test.dart | 30 +++++++++++++++++++ .../hermetic/build_ipa_test.dart | 10 +++---- .../hermetic/build_linux_test.dart | 29 ++++++++++++++++++ .../hermetic/build_macos_test.dart | 27 ++++++++++++++++- .../hermetic/build_web_test.dart | 2 ++ .../hermetic/build_windows_test.dart | 22 ++++++++++++++ .../test/general.shard/base/os_test.dart | 22 +++++++++++++- .../test/general.shard/utils_test.dart | 11 +++++++ packages/flutter_tools/test/src/fakes.dart | 3 ++ 20 files changed, 262 insertions(+), 35 deletions(-) diff --git a/dev/devicelab/lib/tasks/run_tests.dart b/dev/devicelab/lib/tasks/run_tests.dart index f54d4334b54..09d2009b2ed 100644 --- a/dev/devicelab/lib/tasks/run_tests.dart +++ b/dev/devicelab/lib/tasks/run_tests.dart @@ -135,7 +135,7 @@ class AndroidRunOutputTest extends RunOutputTask { _findNextMatcherInList( stdout, (String line) => line.contains('Built build/app/outputs/flutter-apk/$apk') && - (!release || line.contains('MB).')), + (!release || line.contains('MB)')), 'Built build/app/outputs/flutter-apk/$apk', ); diff --git a/packages/flutter_tools/lib/src/android/gradle.dart b/packages/flutter_tools/lib/src/android/gradle.dart index 12a92d4bd81..78fb5d9f4c5 100644 --- a/packages/flutter_tools/lib/src/android/gradle.dart +++ b/packages/flutter_tools/lib/src/android/gradle.dart @@ -549,14 +549,15 @@ class AndroidGradleBuilder implements AndroidBuilder { final File bundleFile = findBundleFile(project, buildInfo, _logger, _usage, _analytics); final String appSize = (buildInfo.mode == BuildMode.debug) ? '' // Don't display the size when building a debug variant. - : ' (${getSizeAsMB(bundleFile.lengthSync())})'; + : ' (${getSizeAsPlatformMB(bundleFile.lengthSync())})'; if (buildInfo.codeSizeDirectory != null) { await _performCodeSizeAnalysis('aab', bundleFile, androidBuildInfo); } _logger.printStatus( - '${_logger.terminal.successMark} Built ${_fileSystem.path.relative(bundleFile.path)}$appSize.', + '${_logger.terminal.successMark} ' + 'Built ${_fileSystem.path.relative(bundleFile.path)}$appSize', color: TerminalColor.green, ); return; @@ -586,9 +587,10 @@ class AndroidGradleBuilder implements AndroidBuilder { final String appSize = (buildInfo.mode == BuildMode.debug) ? '' // Don't display the size when building a debug variant. - : ' (${getSizeAsMB(apkFile.lengthSync())})'; + : ' (${getSizeAsPlatformMB(apkFile.lengthSync())})'; _logger.printStatus( - '${_logger.terminal.successMark} Built ${_fileSystem.path.relative(apkFile.path)}$appSize.', + '${_logger.terminal.successMark} ' + 'Built ${_fileSystem.path.relative(apkFile.path)}$appSize', color: TerminalColor.green, ); @@ -780,7 +782,8 @@ class AndroidGradleBuilder implements AndroidBuilder { ); } _logger.printStatus( - '${_logger.terminal.successMark} Built ${_fileSystem.path.relative(repoDirectory.path)}.', + '${_logger.terminal.successMark} ' + 'Built ${_fileSystem.path.relative(repoDirectory.path)}', color: TerminalColor.green, ); } diff --git a/packages/flutter_tools/lib/src/base/os.dart b/packages/flutter_tools/lib/src/base/os.dart index eb9847cf139..c2ecba17d73 100644 --- a/packages/flutter_tools/lib/src/base/os.dart +++ b/packages/flutter_tools/lib/src/base/os.dart @@ -105,6 +105,18 @@ abstract class OperatingSystemUtils { /// Return the File representing a new pipe. File makePipe(String path); + /// Return a directory's total size in bytes. + int? getDirectorySize(Directory directory) { + int? size; + for (final FileSystemEntity entity in directory.listSync(recursive: true, followLinks: false)) { + if (entity is File) { + size ??= 0; + size += entity.lengthSync(); + } + } + return size; + } + void unzip(File file, Directory targetDirectory); void unpack(File gzippedTarFile, Directory targetDirectory); diff --git a/packages/flutter_tools/lib/src/base/utils.dart b/packages/flutter_tools/lib/src/base/utils.dart index 69ee38c28f9..16ca5faf22b 100644 --- a/packages/flutter_tools/lib/src/base/utils.dart +++ b/packages/flutter_tools/lib/src/base/utils.dart @@ -7,9 +7,11 @@ import 'dart:math' as math; import 'package:file/file.dart'; import 'package:intl/intl.dart'; +import 'package:meta/meta.dart'; import 'package:path/path.dart' as path; // flutter_ignore: package_path_import import '../convert.dart'; +import 'platform.dart'; /// A path jointer for URL paths. final path.Context urlContext = path.url; @@ -88,9 +90,14 @@ String getElapsedAsMilliseconds(Duration duration) { return '${kMillisecondsFormat.format(duration.inMilliseconds)}ms'; } -/// Return a String - with units - for the size in MB of the given number of bytes. -String getSizeAsMB(int bytesLength) { - return '${(bytesLength / (1024 * 1024)).toStringAsFixed(1)}MB'; +/// Return a platform-appropriate [String] representing the size of the given number of bytes. +String getSizeAsPlatformMB(int bytesLength, { + @visibleForTesting Platform platform = const LocalPlatform() + }) { + // Because Windows displays 'MB' but actually reports MiB, we calculate MiB + // accordingly on Windows. + final int bytesInPlatformMB = platform.isWindows ? 1024 * 1024 : 1000 * 1000; + return '${(bytesLength / bytesInPlatformMB).toStringAsFixed(1)}MB'; } /// A class to maintain a list of items, fire events when items are added or diff --git a/packages/flutter_tools/lib/src/commands/build_ios.dart b/packages/flutter_tools/lib/src/commands/build_ios.dart index 241413f5ef7..acf34b7c092 100644 --- a/packages/flutter_tools/lib/src/commands/build_ios.dart +++ b/packages/flutter_tools/lib/src/commands/build_ios.dart @@ -5,15 +5,16 @@ import 'dart:typed_data'; import 'package:crypto/crypto.dart'; -import 'package:file/file.dart'; import 'package:meta/meta.dart'; import 'package:unified_analytics/unified_analytics.dart'; import '../base/analyze_size.dart'; import '../base/common.dart'; import '../base/error_handling_io.dart'; +import '../base/file_system.dart'; import '../base/logger.dart'; import '../base/process.dart'; +import '../base/terminal.dart'; import '../base/utils.dart'; import '../build_info.dart'; import '../convert.dart'; @@ -523,7 +524,17 @@ class BuildIOSArchiveCommand extends _BuildIOSSubCommand { return FlutterCommandResult.success(); } - globals.printStatus('Built IPA to $absoluteOutputPath.'); + final Directory outputDirectory = globals.fs.directory(absoluteOutputPath); + final int? directorySize = globals.os.getDirectorySize(outputDirectory); + final String appSize = (buildInfo.mode == BuildMode.debug || directorySize == null) + ? '' // Don't display the size when building a debug variant. + : ' (${getSizeAsPlatformMB(directorySize)})'; + + globals.printStatus( + '${globals.terminal.successMark} ' + 'Built IPA to ${globals.fs.path.relative(outputDirectory.path)}$appSize', + color: TerminalColor.green, + ); if (isAppStoreUpload) { globals.printStatus('To upload to the App Store either:'); @@ -737,7 +748,17 @@ abstract class _BuildIOSSubCommand extends BuildSubCommand { } if (result.output != null) { - globals.printStatus('Built ${result.output}.'); + final Directory outputDirectory = globals.fs.directory(result.output); + final int? directorySize = globals.os.getDirectorySize(outputDirectory); + final String appSize = (buildInfo.mode == BuildMode.debug || directorySize == null) + ? '' // Don't display the size when building a debug variant. + : ' (${getSizeAsPlatformMB(directorySize)})'; + + globals.printStatus( + '${globals.terminal.successMark} ' + 'Built ${globals.fs.path.relative(outputDirectory.path)}$appSize', + color: TerminalColor.green, + ); // When an app is successfully built, record to analytics whether Impeller // is enabled or disabled. diff --git a/packages/flutter_tools/lib/src/isolated/resident_web_runner.dart b/packages/flutter_tools/lib/src/isolated/resident_web_runner.dart index ab3b7eeb62f..62285209b4f 100644 --- a/packages/flutter_tools/lib/src/isolated/resident_web_runner.dart +++ b/packages/flutter_tools/lib/src/isolated/resident_web_runner.dart @@ -577,7 +577,7 @@ Please provide a valid TCP port (an integer between 0 and 65535, inclusive). shaderCompiler: device!.developmentShaderCompiler, ); devFSStatus.stop(); - _logger.printTrace('Synced ${getSizeAsMB(report.syncedBytes)}.'); + _logger.printTrace('Synced ${getSizeAsPlatformMB(report.syncedBytes)}.'); return report; } diff --git a/packages/flutter_tools/lib/src/linux/build_linux.dart b/packages/flutter_tools/lib/src/linux/build_linux.dart index 808130a24fc..1a5e886a7b6 100644 --- a/packages/flutter_tools/lib/src/linux/build_linux.dart +++ b/packages/flutter_tools/lib/src/linux/build_linux.dart @@ -10,6 +10,7 @@ import '../base/common.dart'; import '../base/file_system.dart'; import '../base/logger.dart'; import '../base/project_migrator.dart'; +import '../base/terminal.dart'; import '../base/utils.dart'; import '../build_info.dart'; import '../cache.dart'; @@ -73,16 +74,31 @@ Future buildLinux( final Status status = logger.startProgress( 'Building Linux application...', ); + final String buildModeName = buildInfo.mode.cliName; + final Directory platformBuildDirectory = globals.fs.directory(getLinuxBuildDirectory(targetPlatform)); + final Directory buildDirectory = platformBuildDirectory.childDirectory(buildModeName); try { - final String buildModeName = buildInfo.mode.cliName; - final Directory buildDirectory = - globals.fs.directory(getLinuxBuildDirectory(targetPlatform)).childDirectory(buildModeName); await _runCmake(buildModeName, linuxProject.cmakeFile.parent, buildDirectory, needCrossBuild, targetPlatform, targetSysroot); await _runBuild(buildDirectory); } finally { status.cancel(); } + + final String? binaryName = getCmakeExecutableName(linuxProject); + final File binaryFile = buildDirectory + .childDirectory('bundle') + .childFile('$binaryName'); + final FileSystemEntity buildOutput = binaryFile.existsSync() ? binaryFile : binaryFile.parent; + // We don't print a size because the output directory can contain + // optional files not needed by the user and because the binary is not + // self-contained. + globals.printStatus( + '${globals.terminal.successMark} ' + 'Built ${globals.fs.path.relative(buildOutput.path)}', + color: TerminalColor.green, + ); + if (buildInfo.codeSizeDirectory != null && sizeAnalyzer != null) { final String arch = getNameForTargetPlatform(targetPlatform); final File codeSizeFile = globals.fs.directory(buildInfo.codeSizeDirectory) diff --git a/packages/flutter_tools/lib/src/macos/build_macos.dart b/packages/flutter_tools/lib/src/macos/build_macos.dart index 146477b63b5..88cfd375bff 100644 --- a/packages/flutter_tools/lib/src/macos/build_macos.dart +++ b/packages/flutter_tools/lib/src/macos/build_macos.dart @@ -9,6 +9,8 @@ import '../base/common.dart'; import '../base/file_system.dart'; import '../base/logger.dart'; import '../base/project_migrator.dart'; +import '../base/terminal.dart'; +import '../base/utils.dart'; import '../build_info.dart'; import '../convert.dart'; import '../globals.dart' as globals; @@ -18,6 +20,7 @@ import '../migrations/xcode_project_object_version_migration.dart'; import '../migrations/xcode_script_build_phase_migration.dart'; import '../migrations/xcode_thin_binary_build_phase_input_paths_migration.dart'; import '../project.dart'; +import 'application_package.dart'; import 'cocoapod_utils.dart'; import 'migrations/flutter_application_migration.dart'; import 'migrations/macos_deployment_target_migration.dart'; @@ -158,9 +161,24 @@ Future buildMacOS({ } finally { status.cancel(); } + if (result != 0) { throwToolExit('Build process failed'); } + final String? applicationBundle = MacOSApp.fromMacOSProject(flutterProject.macos).applicationBundle(buildInfo); + if (applicationBundle != null) { + final Directory outputDirectory = globals.fs.directory(applicationBundle); + // This output directory is the .app folder itself. + final int? directorySize = globals.os.getDirectorySize(outputDirectory); + final String appSize = (buildInfo.mode == BuildMode.debug || directorySize == null) + ? '' // Don't display the size when building a debug variant. + : ' (${getSizeAsPlatformMB(directorySize)})'; + globals.printStatus( + '${globals.terminal.successMark} ' + 'Built ${globals.fs.path.relative(outputDirectory.path)}$appSize', + color: TerminalColor.green, + ); + } await _writeCodeSizeAnalysis(buildInfo, sizeAnalyzer); final Duration elapsedDuration = sw.elapsed; globals.flutterUsage.sendTiming('build', 'xcode-macos', elapsedDuration); diff --git a/packages/flutter_tools/lib/src/resident_runner.dart b/packages/flutter_tools/lib/src/resident_runner.dart index 4e9b38bfec1..6679e654919 100644 --- a/packages/flutter_tools/lib/src/resident_runner.dart +++ b/packages/flutter_tools/lib/src/resident_runner.dart @@ -597,7 +597,7 @@ class FlutterDevice { return UpdateFSReport(); } devFSStatus.stop(); - globals.printTrace('Synced ${getSizeAsMB(report.syncedBytes)}.'); + globals.printTrace('Synced ${getSizeAsPlatformMB(report.syncedBytes)}.'); return report; } diff --git a/packages/flutter_tools/lib/src/web/compile.dart b/packages/flutter_tools/lib/src/web/compile.dart index 27dedc063bd..f34602158f3 100644 --- a/packages/flutter_tools/lib/src/web/compile.dart +++ b/packages/flutter_tools/lib/src/web/compile.dart @@ -10,6 +10,7 @@ import '../base/common.dart'; import '../base/file_system.dart'; import '../base/logger.dart'; import '../base/project_migrator.dart'; +import '../base/terminal.dart'; import '../base/utils.dart'; import '../build_info.dart'; import '../build_system/build_system.dart'; @@ -130,6 +131,14 @@ class WebBuilder { status.stop(); } + // We don't print a size because the output directory can contain + // optional files not needed by the user. + globals.printStatus( + '${globals.terminal.successMark} ' + 'Built ${globals.fs.path.relative(outputDirectory.path)}', + color: TerminalColor.green, + ); + final String buildSettingsString = _buildEventAnalyticsSettings( configs: compilerConfigs, ); diff --git a/packages/flutter_tools/lib/src/windows/build_windows.dart b/packages/flutter_tools/lib/src/windows/build_windows.dart index 813100dd57d..5072be39595 100644 --- a/packages/flutter_tools/lib/src/windows/build_windows.dart +++ b/packages/flutter_tools/lib/src/windows/build_windows.dart @@ -114,20 +114,19 @@ Future buildWindows( } final String? binaryName = getCmakeExecutableName(windowsProject); - final File appFile = buildDirectory + final File binaryFile = buildDirectory .childDirectory('runner') .childDirectory(sentenceCase(buildModeName)) .childFile('$binaryName.exe'); - if (appFile.existsSync()) { - final String appSize = (buildInfo.mode == BuildMode.debug) - ? '' // Don't display the size when building a debug variant. - : ' (${getSizeAsMB(appFile.lengthSync())})'; - globals.logger.printStatus( - '${globals.logger.terminal.successMark} ' - 'Built ${globals.fs.path.relative(appFile.path)}$appSize.', - color: TerminalColor.green, - ); - } + final FileSystemEntity buildOutput = binaryFile.existsSync() ? binaryFile : binaryFile.parent; + // We don't print a size because the output directory can contain + // optional files not needed by the user and because the binary is not + // self-contained. + globals.logger.printStatus( + '${globals.logger.terminal.successMark} ' + 'Built ${globals.fs.path.relative(buildOutput.path)}', + color: TerminalColor.green, + ); if (buildInfo.codeSizeDirectory != null && sizeAnalyzer != null) { final String arch = getNameForTargetPlatform(targetPlatform); diff --git a/packages/flutter_tools/test/commands.shard/hermetic/build_ios_test.dart b/packages/flutter_tools/test/commands.shard/hermetic/build_ios_test.dart index 9adee59ee04..238121a6102 100644 --- a/packages/flutter_tools/test/commands.shard/hermetic/build_ios_test.dart +++ b/packages/flutter_tools/test/commands.shard/hermetic/build_ios_test.dart @@ -282,6 +282,36 @@ void main() { XcodeProjectInterpreter: () => FakeXcodeProjectInterpreterWithBuildSettings(), }); + + testUsingContext('ios build outputs path and size when successful', () async { + final BuildCommand command = BuildCommand( + artifacts: artifacts, + androidSdk: FakeAndroidSdk(), + buildSystem: TestBuildSystem.all(BuildResult(success: true)), + fileSystem: MemoryFileSystem.test(), + logger: BufferLogger.test(), + processUtils: processUtils, + osUtils: FakeOperatingSystemUtils(), + ); + createMinimalMockProjectFiles(); + + await createTestCommandRunner(command).run( + const ['build', 'ios', '--no-pub'] + ); + expect(testLogger.statusText, contains(RegExp(r'✓ Built build/ios/iphoneos/Runner\.app \(\d+\.\d+MB\)'))); + }, overrides: { + FileSystem: () => fileSystem, + ProcessManager: () => FakeProcessManager.list([ + xattrCommand, + setUpFakeXcodeBuildHandler(onRun: (_) { + fileSystem.directory('build/ios/Release-iphoneos/Runner.app').createSync(recursive: true); + }), + setUpRsyncCommand(), + ]), + Platform: () => macosPlatform, + XcodeProjectInterpreter: () => FakeXcodeProjectInterpreterWithBuildSettings(), + }); + testUsingContext('ios build invokes xcode build', () async { final BuildCommand command = BuildCommand( artifacts: artifacts, diff --git a/packages/flutter_tools/test/commands.shard/hermetic/build_ipa_test.dart b/packages/flutter_tools/test/commands.shard/hermetic/build_ipa_test.dart index a90f414d01d..a1a86dc7ec5 100644 --- a/packages/flutter_tools/test/commands.shard/hermetic/build_ipa_test.dart +++ b/packages/flutter_tools/test/commands.shard/hermetic/build_ipa_test.dart @@ -576,7 +576,7 @@ void main() { expect(logger.statusText, contains('build/ios/archive/Runner.xcarchive')); expect(logger.statusText, contains('Building App Store IPA')); - expect(logger.statusText, contains('Built IPA to /build/ios/ipa')); + expect(logger.statusText, contains(RegExp(r'Built IPA to build/ios/ipa \(\d+\.\d+MB\)'))); expect(logger.statusText, contains('To upload to the App Store')); expect(logger.statusText, contains('Apple Transporter macOS app')); expect(fakeProcessManager, hasNoRemainingExpectations); @@ -628,7 +628,7 @@ void main() { expect(logger.statusText, contains('build/ios/archive/Runner.xcarchive')); expect(logger.statusText, contains('Building ad-hoc IPA')); - expect(logger.statusText, contains('Built IPA to /build/ios/ipa')); + expect(logger.statusText, contains(RegExp(r'Built IPA to build/ios/ipa \(\d+\.\d+MB\)'))); // Don'ltruct how to upload to the App Store. expect(logger.statusText, isNot(contains('To upload'))); expect(fakeProcessManager, hasNoRemainingExpectations); @@ -680,7 +680,7 @@ void main() { expect(logger.statusText, contains('build/ios/archive/Runner.xcarchive')); expect(logger.statusText, contains('Building enterprise IPA')); - expect(logger.statusText, contains('Built IPA to /build/ios/ipa')); + expect(logger.statusText, contains(RegExp(r'Built IPA to build/ios/ipa \(\d+\.\d+MB\)'))); // Don'ltruct how to upload to the App Store. expect(logger.statusText, isNot(contains('To upload'))); expect(fakeProcessManager, hasNoRemainingExpectations); @@ -889,7 +889,7 @@ void main() { testUsingContext('ipa build invokes xcode build export archive when passed plist', () async { final String outputPath = - fileSystem.path.absolute(fileSystem.path.join('build', 'ios', 'ipa')); + fileSystem.path.relative(fileSystem.path.join('build', 'ios', 'ipa')); final File exportOptions = fileSystem.file('ExportOptions.plist') ..createSync(); final BuildCommand command = BuildCommand( @@ -918,7 +918,7 @@ void main() { ], ); - expect(logger.statusText, contains('Built IPA to $outputPath.')); + expect(logger.statusText, contains(RegExp('Built IPA to $outputPath ' r'\(\d+\.\d+MB\)'))); expect(fakeProcessManager, hasNoRemainingExpectations); }, overrides: { FileSystem: () => fileSystem, diff --git a/packages/flutter_tools/test/commands.shard/hermetic/build_linux_test.dart b/packages/flutter_tools/test/commands.shard/hermetic/build_linux_test.dart index 5232513cdbb..666aa2fce9e 100644 --- a/packages/flutter_tools/test/commands.shard/hermetic/build_linux_test.dart +++ b/packages/flutter_tools/test/commands.shard/hermetic/build_linux_test.dart @@ -194,6 +194,35 @@ void main() { FeatureFlags: () => TestFeatureFlags(), }); + testUsingContext('Linux build outputs path when successful', () async { + final BuildCommand command = BuildCommand( + artifacts: artifacts, + androidSdk: FakeAndroidSdk(), + buildSystem: TestBuildSystem.all(BuildResult(success: true)), + fileSystem: MemoryFileSystem.test(), + logger: BufferLogger.test(), + processUtils: processUtils, + osUtils: FakeOperatingSystemUtils(), + ); + processManager = FakeProcessManager.list([ + cmakeCommand('release'), + ninjaCommand('release'), + ]); + + setUpMockProjectFilesForBuild(); + + await createTestCommandRunner(command).run( + const ['build', 'linux', '--no-pub'] + ); + expect(testLogger.statusText, contains('✓ Built build/linux/x64/release/bundle')); + }, overrides: { + FileSystem: () => fileSystem, + ProcessManager: () => processManager, + Platform: () => linuxPlatform, + FeatureFlags: () => TestFeatureFlags(isLinuxEnabled: true), + OperatingSystemUtils: () => FakeOperatingSystemUtils(), + }); + testUsingContext('Linux build invokes CMake and ninja, and writes temporary files', () async { final BuildCommand command = BuildCommand( artifacts: artifacts, diff --git a/packages/flutter_tools/test/commands.shard/hermetic/build_macos_test.dart b/packages/flutter_tools/test/commands.shard/hermetic/build_macos_test.dart index d486e6efa8b..6e4a1fa18c5 100644 --- a/packages/flutter_tools/test/commands.shard/hermetic/build_macos_test.dart +++ b/packages/flutter_tools/test/commands.shard/hermetic/build_macos_test.dart @@ -301,6 +301,31 @@ STDERR STUFF FeatureFlags: () => TestFeatureFlags(isMacOSEnabled: true), }); + testUsingContext('macOS build outputs path and size when successful', () async { + final BuildCommand command = BuildCommand( + artifacts: artifacts, + androidSdk: FakeAndroidSdk(), + buildSystem: TestBuildSystem.all(BuildResult(success: true)), + fileSystem: MemoryFileSystem.test(), + processUtils: processUtils, + logger: BufferLogger.test(), + osUtils: FakeOperatingSystemUtils(), + ); + createMinimalMockProjectFiles(); + + await createTestCommandRunner(command).run( + const ['build', 'macos', '--no-pub'] + ); + expect(testLogger.statusText, contains(RegExp(r'✓ Built build/macos/Build/Products/Release/example.app \(\d+\.\d+MB\)'))); + }, overrides: { + FileSystem: () => fileSystem, + ProcessManager: () => FakeProcessManager.list([ + setUpFakeXcodeBuildHandler('Release'), + ]), + Platform: () => macosPlatform, + FeatureFlags: () => TestFeatureFlags(isMacOSEnabled: true), + }); + testUsingContext('macOS build invokes xcode build (debug)', () async { final BuildCommand command = BuildCommand( artifacts: artifacts, @@ -639,7 +664,7 @@ STDERR STUFF ); createMinimalMockProjectFiles(); - fileSystem.file('build/macos/Build/Products/Release/Runner.app/App') + fileSystem.file('build/macos/Build/Products/Release/example.app/App') ..createSync(recursive: true) ..writeAsBytesSync(List.generate(10000, (int index) => 0)); diff --git a/packages/flutter_tools/test/commands.shard/hermetic/build_web_test.dart b/packages/flutter_tools/test/commands.shard/hermetic/build_web_test.dart index 15a8ed0e221..2cfee6db340 100644 --- a/packages/flutter_tools/test/commands.shard/hermetic/build_web_test.dart +++ b/packages/flutter_tools/test/commands.shard/hermetic/build_web_test.dart @@ -140,6 +140,7 @@ void main() { final Directory buildDir = fileSystem.directory(fileSystem.path.join('build', 'web')); expect(buildDir.existsSync(), true); + expect(testLogger.statusText, contains('✓ Built ${buildDir.path}')); }, overrides: { Platform: () => fakePlatform, FileSystem: () => fileSystem, @@ -236,6 +237,7 @@ void main() { ]); expect(buildDir.existsSync(), true); + expect(testLogger.statusText, contains('✓ Built $newBuildDir')); }, overrides: { Platform: () => fakePlatform, FileSystem: () => fileSystem, diff --git a/packages/flutter_tools/test/commands.shard/hermetic/build_windows_test.dart b/packages/flutter_tools/test/commands.shard/hermetic/build_windows_test.dart index c24f72a65ac..d6a31f3b8ed 100644 --- a/packages/flutter_tools/test/commands.shard/hermetic/build_windows_test.dart +++ b/packages/flutter_tools/test/commands.shard/hermetic/build_windows_test.dart @@ -570,6 +570,28 @@ if %errorlevel% neq 0 goto :VCEnd FeatureFlags: () => TestFeatureFlags(isWindowsEnabled: true), }); + testUsingContext('Windows build outputs path when successful', () async { + final FakeVisualStudio fakeVisualStudio = FakeVisualStudio(); + final BuildWindowsCommand command = BuildWindowsCommand(logger: BufferLogger.test(), operatingSystemUtils: FakeOperatingSystemUtils()) + ..visualStudioOverride = fakeVisualStudio; + setUpMockProjectFilesForBuild(); + + processManager = FakeProcessManager.list([ + cmakeGenerationCommand(), + buildCommand('Release'), + ]); + + await createTestCommandRunner(command).run( + const ['windows', '--release', '--no-pub'] + ); + expect(testLogger.statusText, contains(r'✓ Built build\windows\x64\runner\Release')); + }, overrides: { + FileSystem: () => fileSystem, + ProcessManager: () => processManager, + Platform: () => windowsPlatform, + FeatureFlags: () => TestFeatureFlags(isWindowsEnabled: true), + }); + testUsingContext('Windows build passes correct generator', () async { const String generator = 'A different generator'; final FakeVisualStudio fakeVisualStudio = FakeVisualStudio( diff --git a/packages/flutter_tools/test/general.shard/base/os_test.dart b/packages/flutter_tools/test/general.shard/base/os_test.dart index ee95332100e..1472b875228 100644 --- a/packages/flutter_tools/test/general.shard/base/os_test.dart +++ b/packages/flutter_tools/test/general.shard/base/os_test.dart @@ -751,7 +751,7 @@ void main() { ); expect( - () => unknownOsUtils.unzip(fileSystem.file('foo.zip'), fileSystem.currentDirectory), + () => unknownOsUtils.unzip(fileSystem.file('foo.zip'), fileSystem.currentDirectory), throwsToolExit (message: 'Missing "unzip" tool. Unable to extract foo.zip.\n' 'Please install unzip.'), @@ -759,6 +759,26 @@ void main() { }); }); + testWithoutContext('directory size', () { + final FileSystem fileSystem = MemoryFileSystem.test(); + final OperatingSystemUtils osUtils = OperatingSystemUtils( + fileSystem: fileSystem, + logger: BufferLogger.test(), + platform: FakePlatform(operatingSystem: 'fuchsia'), + processManager: fakeProcessManager, + ); + + final Directory directory = fileSystem.systemTempDirectory.childDirectory('test_directory'); + directory.createSync(); + directory.childFile('file1.txt').writeAsBytesSync(List.filled(10, 0)); + directory.childFile('file2.txt').writeAsBytesSync(List.filled(20, 0)); + final Directory subDirectory = directory.childDirectory('sub_directory'); + subDirectory.createSync(); + subDirectory.childFile('file3.txt').writeAsBytesSync(List.filled(15, 0)); + + expect(osUtils.getDirectorySize(directory), equals(10 + 20 + 15)); + }); + testWithoutContext('stream compression level', () { expect(OperatingSystemUtils.gzipLevel1.level, equals(1)); }); diff --git a/packages/flutter_tools/test/general.shard/utils_test.dart b/packages/flutter_tools/test/general.shard/utils_test.dart index 3fa94cb1c77..da12e4f6814 100644 --- a/packages/flutter_tools/test/general.shard/utils_test.dart +++ b/packages/flutter_tools/test/general.shard/utils_test.dart @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +import 'package:flutter_tools/src/base/platform.dart'; import 'package:flutter_tools/src/base/terminal.dart'; import 'package:flutter_tools/src/base/utils.dart'; import 'package:flutter_tools/src/base/version.dart'; @@ -406,4 +407,14 @@ needs to be wrapped. ); }); }); + + testWithoutContext('getSizeAsMB', () async { + // ignore: avoid_redundant_argument_values + expect(getSizeAsPlatformMB(10 * 1000 * 1000, platform: FakePlatform(operatingSystem: 'linux')), '10.0MB'); + expect(getSizeAsPlatformMB(10 * 1000 * 1000, platform: FakePlatform(operatingSystem: 'macos')), '10.0MB'); + expect(getSizeAsPlatformMB(10 * 1000 * 1000, platform: FakePlatform(operatingSystem: 'windows')), '9.5MB'); + expect(getSizeAsPlatformMB(10 * 1000 * 1000, platform: FakePlatform(operatingSystem: 'android')), '10.0MB'); + expect(getSizeAsPlatformMB(10 * 1000 * 1000, platform: FakePlatform(operatingSystem: 'ios')), '10.0MB'); + expect(getSizeAsPlatformMB(10 * 1000 * 1000, platform: FakePlatform(operatingSystem: 'web')), '10.0MB'); + }); } diff --git a/packages/flutter_tools/test/src/fakes.dart b/packages/flutter_tools/test/src/fakes.dart index 1809d3d4c2c..feddd15fba2 100644 --- a/packages/flutter_tools/test/src/fakes.dart +++ b/packages/flutter_tools/test/src/fakes.dart @@ -550,6 +550,9 @@ class FakeOperatingSystemUtils extends Fake implements OperatingSystemUtils { @override List whichAll(String execName) => []; + @override + int? getDirectorySize(Directory directory) => 10000000; // 10 MB / 9.5 MiB + @override void unzip(File file, Directory targetDirectory) { }