[flutter_tools] tool exit access denied during symlinking (#106213)
diff --git a/packages/flutter_tools/lib/src/flutter_plugins.dart b/packages/flutter_tools/lib/src/flutter_plugins.dart
index 2421143..8950364 100644
--- a/packages/flutter_tools/lib/src/flutter_plugins.dart
+++ b/packages/flutter_tools/lib/src/flutter_plugins.dart
@@ -1004,20 +1004,32 @@
void handleSymlinkException(FileSystemException e, {
required Platform platform,
required OperatingSystemUtils os,
+ required String destination,
+ required String source,
}) {
- if (platform.isWindows && (e.osError?.errorCode ?? 0) == 1314) {
- final String? versionString = RegExp(r'[\d.]+').firstMatch(os.name)?.group(0);
- final Version? version = Version.parse(versionString);
- // Windows 10 14972 is the oldest version that allows creating symlinks
- // just by enabling developer mode; before that it requires running the
- // terminal as Administrator.
- // https://blogs.windows.com/windowsdeveloper/2016/12/02/symlinks-windows-10/
- final String instructions = (version != null && version >= Version(10, 0, 14972))
- ? 'Please enable Developer Mode in your system settings. Run\n'
- ' start ms-settings:developers\n'
- 'to open settings.'
- : 'You must build from a terminal run as administrator.';
- throwToolExit('Building with plugins requires symlink support.\n\n$instructions');
+ if (platform.isWindows) {
+ // ERROR_ACCESS_DENIED
+ if (e.osError?.errorCode == 5) {
+ throwToolExit(
+ 'ERROR_ACCESS_DENIED file system exception thrown while trying to '
+ 'create a symlink from $source to $destination',
+ );
+ }
+ // ERROR_PRIVILEGE_NOT_HELD, user cannot symlink
+ if (e.osError?.errorCode == 1314) {
+ final String? versionString = RegExp(r'[\d.]+').firstMatch(os.name)?.group(0);
+ final Version? version = Version.parse(versionString);
+ // Windows 10 14972 is the oldest version that allows creating symlinks
+ // just by enabling developer mode; before that it requires running the
+ // terminal as Administrator.
+ // https://blogs.windows.com/windowsdeveloper/2016/12/02/symlinks-windows-10/
+ final String instructions = (version != null && version >= Version(10, 0, 14972))
+ ? 'Please enable Developer Mode in your system settings. Run\n'
+ ' start ms-settings:developers\n'
+ 'to open settings.'
+ : 'You must build from a terminal run as administrator.';
+ throwToolExit('Building with plugins requires symlink support.\n\n$instructions');
+ }
}
}
@@ -1043,7 +1055,13 @@
try {
link.createSync(path);
} on FileSystemException catch (e) {
- handleSymlinkException(e, platform: globals.platform, os: globals.os);
+ handleSymlinkException(
+ e,
+ platform: globals.platform,
+ os: globals.os,
+ destination: 'dest',
+ source: 'source',
+ );
rethrow;
}
}
diff --git a/packages/flutter_tools/test/general.shard/plugins_test.dart b/packages/flutter_tools/test/general.shard/plugins_test.dart
index 71c5fb2..7de3ab4 100644
--- a/packages/flutter_tools/test/general.shard/plugins_test.dart
+++ b/packages/flutter_tools/test/general.shard/plugins_test.dart
@@ -84,6 +84,8 @@
// using it instead of fs must re-run any necessary setup (e.g.,
// setUpProject).
late FileSystem fsWindows;
+ const String pubCachePath = '/path/to/.pub-cache/hosted/pub.dartlang.org/foo-1.2.3';
+ const String ephemeralPackagePath = '/path/to/app/linux/flutter/ephemeral/foo-1.2.3';
// Adds basic properties to the flutterProject and its subprojects.
void setUpProject(FileSystem fileSystem) {
@@ -1612,8 +1614,36 @@
const FileSystemException e = FileSystemException('', '', OSError('', 1314));
- expect(() => handleSymlinkException(e, platform: platform, os: os),
- throwsToolExit(message: 'start ms-settings:developers'));
+ expect(
+ () => handleSymlinkException(
+ e,
+ platform: platform,
+ os: os,
+ source: pubCachePath,
+ destination: ephemeralPackagePath,
+ ),
+ throwsToolExit(message: 'start ms-settings:developers'),
+ );
+ });
+
+ testWithoutContext('Symlink ERROR_ACCESS_DENIED failures show developers paths that were used', () async {
+ final Platform platform = FakePlatform(operatingSystem: 'windows');
+ final FakeOperatingSystemUtils os = FakeOperatingSystemUtils('Microsoft Windows [Version 10.0.14972.1]');
+
+ const FileSystemException e = FileSystemException('', '', OSError('', 5));
+
+ expect(
+ () => handleSymlinkException(
+ e,
+ platform: platform,
+ os: os,
+ source: pubCachePath,
+ destination: ephemeralPackagePath,
+ ),
+ throwsToolExit(
+ message: 'ERROR_ACCESS_DENIED file system exception thrown while trying to create a symlink from $pubCachePath to $ephemeralPackagePath',
+ ),
+ );
});
testWithoutContext('Symlink failures instruct developers to run as administrator on older versions of Windows', () async {
@@ -1622,8 +1652,16 @@
const FileSystemException e = FileSystemException('', '', OSError('', 1314));
- expect(() => handleSymlinkException(e, platform: platform, os: os),
- throwsToolExit(message: 'administrator'));
+ expect(
+ () => handleSymlinkException(
+ e,
+ platform: platform,
+ os: os,
+ source: pubCachePath,
+ destination: ephemeralPackagePath,
+ ),
+ throwsToolExit(message: 'administrator'),
+ );
});
testWithoutContext('Symlink failures only give instructions for specific errors', () async {
@@ -1632,7 +1670,16 @@
const FileSystemException e = FileSystemException('', '', OSError('', 999));
- expect(() => handleSymlinkException(e, platform: platform, os: os), returnsNormally);
+ expect(
+ () => handleSymlinkException(
+ e,
+ platform: platform,
+ os: os,
+ source: pubCachePath,
+ destination: ephemeralPackagePath,
+ ),
+ returnsNormally,
+ );
});
});
}