From af913a75b5d28b03f0bfe0ee44f9343ae26ae146 Mon Sep 17 00:00:00 2001 From: Andrew Kolos Date: Tue, 2 Jul 2024 11:46:22 -0700 Subject: [PATCH] Use `ErrorHandlingFileSystem.deleteIfExists` when deleting .plugin_symlinks (#151073) Fixes https://github.com/flutter/flutter/issues/137168. --- .../lib/src/base/error_handling_io.dart | 6 +- .../lib/src/flutter_plugins.dart | 4 +- .../test/general.shard/cache_test.dart | 2 +- .../test/general.shard/plugins_test.dart | 69 +++++++++++++++++++ 4 files changed, 75 insertions(+), 6 deletions(-) diff --git a/packages/flutter_tools/lib/src/base/error_handling_io.dart b/packages/flutter_tools/lib/src/base/error_handling_io.dart index 463594e0b73..6166ca0e626 100644 --- a/packages/flutter_tools/lib/src/base/error_handling_io.dart +++ b/packages/flutter_tools/lib/src/base/error_handling_io.dart @@ -95,9 +95,9 @@ class ErrorHandlingFileSystem extends ForwardingFileSystem { } if (entity.existsSync()) { throwToolExit( - 'The Flutter tool tried to delete the file or directory ${entity.path} but was ' - "unable to. This may be due to the file and/or project's location on a read-only " - 'volume. Consider relocating the project and trying again', + 'Unable to delete file or directory at "${entity.path}". ' + 'This may be due to the project being in a read-only ' + 'volume. Consider relocating the project and trying again.', ); } } diff --git a/packages/flutter_tools/lib/src/flutter_plugins.dart b/packages/flutter_tools/lib/src/flutter_plugins.dart index 4e47e50e54c..d154918e3d7 100644 --- a/packages/flutter_tools/lib/src/flutter_plugins.dart +++ b/packages/flutter_tools/lib/src/flutter_plugins.dart @@ -966,9 +966,9 @@ void handleSymlinkException(FileSystemException e, { /// /// If [force] is true, the directory will be created only if missing. void _createPlatformPluginSymlinks(Directory symlinkDirectory, List? platformPlugins, {bool force = false}) { - if (force && symlinkDirectory.existsSync()) { + if (force) { // Start fresh to avoid stale links. - symlinkDirectory.deleteSync(recursive: true); + ErrorHandlingFileSystem.deleteIfExists(symlinkDirectory, recursive: true); } symlinkDirectory.createSync(recursive: true); if (platformPlugins == null) { diff --git a/packages/flutter_tools/test/general.shard/cache_test.dart b/packages/flutter_tools/test/general.shard/cache_test.dart index b0726b09b6c..b19633d4b9b 100644 --- a/packages/flutter_tools/test/general.shard/cache_test.dart +++ b/packages/flutter_tools/test/general.shard/cache_test.dart @@ -909,7 +909,7 @@ void main() { handler.addError(webCacheDirectory, FileSystemOp.delete, const FileSystemException('', '', OSError('', 2))); await expectLater(() => webSdk.updateInner(artifactUpdater, fileSystem, FakeOperatingSystemUtils()), throwsToolExit( - message: RegExp('The Flutter tool tried to delete the file or directory cache/bin/cache/flutter_web_sdk but was unable to'), + message: RegExp('Unable to delete file or directory at "cache/bin/cache/flutter_web_sdk"'), )); }); diff --git a/packages/flutter_tools/test/general.shard/plugins_test.dart b/packages/flutter_tools/test/general.shard/plugins_test.dart index 802d382ef6d..ecab96583cf 100644 --- a/packages/flutter_tools/test/general.shard/plugins_test.dart +++ b/packages/flutter_tools/test/general.shard/plugins_test.dart @@ -3,10 +3,12 @@ // found in the LICENSE file. import 'dart:convert'; +import 'dart:io'; import 'package:file/file.dart'; import 'package:file/memory.dart'; import 'package:file_testing/file_testing.dart'; +import 'package:flutter_tools/src/base/error_handling_io.dart'; import 'package:flutter_tools/src/base/os.dart'; import 'package:flutter_tools/src/base/platform.dart'; import 'package:flutter_tools/src/base/time.dart'; @@ -1752,6 +1754,73 @@ The Flutter Preview device does not support the following plugins from your pubs ); }); }); + + testUsingContext('exits tool when deleting .plugin_symlinks fails', () async { + final FakeFlutterProject flutterProject = FakeFlutterProject() + ..directory = globals.fs.currentDirectory.childDirectory('app'); + final FakeFlutterManifest flutterManifest = FakeFlutterManifest(); + final Directory windowsManagedDirectory = flutterProject.directory + .childDirectory('windows') + .childDirectory('flutter'); + final FakeWindowsProject windowsProject = FakeWindowsProject() + ..managedDirectory = windowsManagedDirectory + ..cmakeFile = windowsManagedDirectory.parent.childFile('CMakeLists.txt') + ..generatedPluginCmakeFile = + windowsManagedDirectory.childFile('generated_plugins.mk') + ..pluginSymlinkDirectory = windowsManagedDirectory + .childDirectory('ephemeral') + .childDirectory('.plugin_symlinks') + ..exists = true; + + flutterProject + ..manifest = flutterManifest + ..flutterPluginsFile = + flutterProject.directory.childFile('.flutter-plugins') + ..flutterPluginsDependenciesFile = + flutterProject.directory.childFile('.flutter-plugins-dependencies') + ..windows = windowsProject; + + flutterProject.directory.childFile('.packages').createSync(recursive: true); + + createPluginSymlinks( + flutterProject, + force: true, + featureFlagsOverride: TestFeatureFlags(isWindowsEnabled: true), + ); + + expect( + () => createPluginSymlinks( + flutterProject, + force: true, + featureFlagsOverride: TestFeatureFlags(isWindowsEnabled: true), + ), + throwsToolExit( + message: RegExp('Unable to delete file or directory at ' + r'"C:\\app\\windows\\flutter\\ephemeral\\\.plugin_symlinks"')), + ); + }, overrides: { + FileSystem: () { + final FileExceptionHandler handle = FileExceptionHandler(); + final ErrorHandlingFileSystem fileSystem = ErrorHandlingFileSystem( + platform: FakePlatform(), + delegate: MemoryFileSystem.test( + style: FileSystemStyle.windows, + opHandle: handle.opHandle, + ), + ); + const String symlinkDirectoryPath = r'C:\app\windows\flutter\ephemeral\.plugin_symlinks'; + handle.addError( + fileSystem.directory(symlinkDirectoryPath), + FileSystemOp.delete, + const PathNotFoundException( + symlinkDirectoryPath, + OSError('The system cannot find the path specified.', 3), + ), + ); + return fileSystem; + }, + ProcessManager: () => FakeProcessManager.empty(), + }); } class FakeFlutterManifest extends Fake implements FlutterManifest {