Unverified Commit be6a3688 authored by stuartmorgan's avatar stuartmorgan Committed by GitHub

Improve Windows symlink instructions (#67029)

When the developer doesn't have permission to create symlinks, we
display specific instructions, but they were only correct for recent
versions of Windows 10. This improves them by:
- Giving the correct instructions for older versions.
- For recent versions, adds a command that will deep-link into the
  settings application so that developers don't have to figure out
  where/how to enable developer mode.

Fixes https://github.com/flutter/flutter/issues/66973
parent 8ee2b2f1
......@@ -11,6 +11,9 @@ import 'android/gradle.dart';
import 'base/common.dart';
import 'base/error_handling_io.dart';
import 'base/file_system.dart';
import 'base/os.dart';
import 'base/platform.dart';
import 'base/version.dart';
import 'convert.dart';
import 'dart/package_map.dart';
import 'features.dart';
......@@ -1087,6 +1090,30 @@ void createPluginSymlinks(FlutterProject project, {bool force = false}) {
}
}
/// Handler for symlink failures which provides specific instructions for known
/// failure cases.
@visibleForTesting
void handleSymlinkException(FileSystemException e, {
@required Platform platform,
@required OperatingSystemUtils os,
}) {
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);
// Window 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);
}
}
/// Creates [symlinkDirectory] containing symlinks to each plugin listed in [platformPlugins].
///
/// If [force] is true, the directory will be created only if missing.
......@@ -1109,12 +1136,7 @@ void _createPlatformPluginSymlinks(Directory symlinkDirectory, List<dynamic> pla
try {
link.createSync(path);
} on FileSystemException catch (e) {
if (globals.platform.isWindows && (e.osError?.errorCode ?? 0) == 1314) {
throwToolExit(
'Building with plugins requires symlink support. '
'Please enable Developer Mode in your system settings.\n\n$e'
);
}
handleSymlinkException(e, platform: globals.platform, os: globals.os);
rethrow;
}
}
......
......@@ -7,6 +7,8 @@ import 'dart:convert';
import 'package:file/file.dart';
import 'package:file/memory.dart';
import 'package:file_testing/file_testing.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';
import 'package:flutter_tools/src/base/utils.dart';
import 'package:flutter_tools/src/features.dart';
......@@ -1414,6 +1416,38 @@ flutter:
});
});
testWithoutContext('Symlink failures give developer mode instructions on recent versions of Windows', () async {
final Platform platform = FakePlatform(operatingSystem: 'windows');
final MockOperatingSystemUtils os = MockOperatingSystemUtils();
when(os.name).thenReturn('Microsoft Windows [Version 10.0.14972.1]');
const FileSystemException e = FileSystemException('', '', OSError('', 1314));
expect(() => handleSymlinkException(e, platform: platform, os: os),
throwsToolExit(message: 'start ms-settings:developers'));
});
testWithoutContext('Symlink failures instruct developers to run as administrator on older versions of Windows', () async {
final Platform platform = FakePlatform(operatingSystem: 'windows');
final MockOperatingSystemUtils os = MockOperatingSystemUtils();
when(os.name).thenReturn('Microsoft Windows [Version 10.0.14393]');
const FileSystemException e = FileSystemException('', '', OSError('', 1314));
expect(() => handleSymlinkException(e, platform: platform, os: os),
throwsToolExit(message: 'administrator'));
});
testWithoutContext('Symlink failures only give instructions for specific errors', () async {
final Platform platform = FakePlatform(operatingSystem: 'windows');
final MockOperatingSystemUtils os = MockOperatingSystemUtils();
when(os.name).thenReturn('Microsoft Windows [Version 10.0.14393]');
const FileSystemException e = FileSystemException('', '', OSError('', 999));
expect(() => handleSymlinkException(e, platform: platform, os: os), returnsNormally);
});
});
}
......@@ -1426,3 +1460,4 @@ class MockXcodeProjectInterpreter extends Mock implements XcodeProjectInterprete
class MockWebProject extends Mock implements WebProject {}
class MockWindowsProject extends Mock implements WindowsProject {}
class MockLinuxProject extends Mock implements LinuxProject {}
class MockOperatingSystemUtils extends Mock implements OperatingSystemUtils {}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment