diff --git a/packages/flutter_tools/lib/src/base/os.dart b/packages/flutter_tools/lib/src/base/os.dart index 3fb130ef811..aae9e2da71f 100644 --- a/packages/flutter_tools/lib/src/base/os.dart +++ b/packages/flutter_tools/lib/src/base/os.dart @@ -427,6 +427,45 @@ class _MacOSUtils extends _PosixUtils { } return _hostPlatform!; } + + // unzip, then rsync + @override + void unzip(File file, Directory targetDirectory) { + if (!_processManager.canRun('unzip')) { + // unzip is not available. this error message is modeled after the download + // error in bin/internal/update_dart_sdk.sh + throwToolExit('Missing "unzip" tool. Unable to extract ${file.path}.\nConsider running "brew install unzip".'); + } + if (_processManager.canRun('rsync')) { + final Directory tempDirectory = _fileSystem.systemTempDirectory.createTempSync('flutter_${file.basename}.'); + try { + // Unzip to a temporary directory. + _processUtils.runSync( + ['unzip', '-o', '-q', file.path, '-d', tempDirectory.path], + throwOnError: true, + verboseExceptions: true, + ); + for (final FileSystemEntity unzippedFile in tempDirectory.listSync(followLinks: false)) { + // rsync --delete the unzipped files so files removed from the archive are also removed from the target. + _processUtils.runSync( + ['rsync', '-av', '--delete', unzippedFile.path, targetDirectory.path], + throwOnError: true, + verboseExceptions: true, + ); + } + } finally { + tempDirectory.deleteSync(recursive: true); + } + } else { + // Fall back to just unzipping. + _logger.printTrace('Unable to find rsync, falling back to direct unzipping.'); + _processUtils.runSync( + ['unzip', '-o', '-q', file.path, '-d', targetDirectory.path], + throwOnError: true, + verboseExceptions: true, + ); + } + } } class _WindowsUtils extends OperatingSystemUtils { 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 a7817bd8912..2b5219b727a 100644 --- a/packages/flutter_tools/test/general.shard/base/os_test.dart +++ b/packages/flutter_tools/test/general.shard/base/os_test.dart @@ -4,6 +4,7 @@ import 'package:file/file.dart'; import 'package:file/memory.dart'; +import 'package:file_testing/file_testing.dart'; import 'package:flutter_tools/src/base/file_system.dart'; import 'package:flutter_tools/src/base/logger.dart'; import 'package:flutter_tools/src/base/os.dart'; @@ -536,6 +537,79 @@ void main() { ); }); + group('unzip on macOS', () { + testWithoutContext('falls back to unzip when rsync cannot run', () { + final FileSystem fileSystem = MemoryFileSystem.test(); + fakeProcessManager.excludedExecutables.add('rsync'); + + final BufferLogger logger = BufferLogger.test(); + final OperatingSystemUtils macOSUtils = OperatingSystemUtils( + fileSystem: fileSystem, + logger: logger, + platform: FakePlatform(operatingSystem: 'macos'), + processManager: fakeProcessManager, + ); + + final Directory targetDirectory = fileSystem.currentDirectory; + fakeProcessManager.addCommand(FakeCommand( + command: ['unzip', '-o', '-q', 'foo.zip', '-d', targetDirectory.path], + )); + + macOSUtils.unzip(fileSystem.file('foo.zip'), targetDirectory); + expect(fakeProcessManager, hasNoRemainingExpectations); + expect(logger.traceText, contains('Unable to find rsync')); + }); + + testWithoutContext('unzip and rsyncs', () { + final FileSystem fileSystem = MemoryFileSystem.test(); + + final OperatingSystemUtils macOSUtils = OperatingSystemUtils( + fileSystem: fileSystem, + logger: BufferLogger.test(), + platform: FakePlatform(operatingSystem: 'macos'), + processManager: fakeProcessManager, + ); + + final Directory targetDirectory = fileSystem.currentDirectory; + final Directory tempDirectory = fileSystem.systemTempDirectory.childDirectory('flutter_foo.zip.rand0'); + fakeProcessManager.addCommands([ + FakeCommand( + command: [ + 'unzip', + '-o', + '-q', + 'foo.zip', + '-d', + tempDirectory.path, + ], + onRun: () { + expect(tempDirectory, exists); + tempDirectory.childDirectory('dirA').childFile('fileA').createSync(recursive: true); + tempDirectory.childDirectory('dirB').childFile('fileB').createSync(recursive: true); + }, + ), + FakeCommand(command: [ + 'rsync', + '-av', + '--delete', + tempDirectory.childDirectory('dirA').path, + targetDirectory.path, + ]), + FakeCommand(command: [ + 'rsync', + '-av', + '--delete', + tempDirectory.childDirectory('dirB').path, + targetDirectory.path, + ]), + ]); + + macOSUtils.unzip(fileSystem.file('foo.zip'), fileSystem.currentDirectory); + expect(fakeProcessManager, hasNoRemainingExpectations); + expect(tempDirectory, isNot(exists)); + }); + }); + group('display an install message when unzip cannot be run', () { testWithoutContext('Linux', () { final FileSystem fileSystem = MemoryFileSystem.test();