Unverified Commit f712fecd authored by Jenn Magder's avatar Jenn Magder Committed by GitHub

Migrate visual_studio to null safety (#78942)

parent c49cd271
...@@ -2,11 +2,9 @@ ...@@ -2,11 +2,9 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
// @dart = 2.8
import 'package:meta/meta.dart';
import 'package:process/process.dart'; import 'package:process/process.dart';
import '../base/common.dart';
import '../base/file_system.dart'; import '../base/file_system.dart';
import '../base/io.dart'; import '../base/io.dart';
import '../base/logger.dart'; import '../base/logger.dart';
...@@ -18,10 +16,10 @@ import '../convert.dart'; ...@@ -18,10 +16,10 @@ import '../convert.dart';
/// Encapsulates information about the installed copy of Visual Studio, if any. /// Encapsulates information about the installed copy of Visual Studio, if any.
class VisualStudio { class VisualStudio {
VisualStudio({ VisualStudio({
@required FileSystem fileSystem, required FileSystem fileSystem,
@required ProcessManager processManager, required ProcessManager processManager,
@required Platform platform, required Platform platform,
@required Logger logger, required Logger logger,
}) : _platform = platform, }) : _platform = platform,
_fileSystem = fileSystem, _fileSystem = fileSystem,
_processUtils = ProcessUtils(processManager: processManager, logger: logger); _processUtils = ProcessUtils(processManager: processManager, logger: logger);
...@@ -38,7 +36,7 @@ class VisualStudio { ...@@ -38,7 +36,7 @@ class VisualStudio {
bool get isInstalled => _bestVisualStudioDetails.isNotEmpty; bool get isInstalled => _bestVisualStudioDetails.isNotEmpty;
bool get isAtLeastMinimumVersion { bool get isAtLeastMinimumVersion {
final int installedMajorVersion = _majorVersion; final int? installedMajorVersion = _majorVersion;
return installedMajorVersion != null && installedMajorVersion >= _minimumSupportedVersion; return installedMajorVersion != null && installedMajorVersion >= _minimumSupportedVersion;
} }
...@@ -54,7 +52,7 @@ class VisualStudio { ...@@ -54,7 +52,7 @@ class VisualStudio {
/// The user-friendly version number of the Visual Studio install. /// The user-friendly version number of the Visual Studio install.
/// ///
/// For instance: "15.4.0". /// For instance: "15.4.0".
String get displayVersion { String? get displayVersion {
if (_bestVisualStudioDetails[_catalogKey] == null) { if (_bestVisualStudioDetails[_catalogKey] == null) {
return null; return null;
} }
...@@ -80,7 +78,7 @@ class VisualStudio { ...@@ -80,7 +78,7 @@ class VisualStudio {
if (_bestVisualStudioDetails.isEmpty) { if (_bestVisualStudioDetails.isEmpty) {
return false; return false;
} }
return _bestVisualStudioDetails[_isCompleteKey] as bool ?? true; return _bestVisualStudioDetails[_isCompleteKey] as bool? ?? true;
} }
/// True if Visual Studio is launchable. /// True if Visual Studio is launchable.
...@@ -90,14 +88,14 @@ class VisualStudio { ...@@ -90,14 +88,14 @@ class VisualStudio {
if (_bestVisualStudioDetails.isEmpty) { if (_bestVisualStudioDetails.isEmpty) {
return false; return false;
} }
return _bestVisualStudioDetails[_isLaunchableKey] as bool ?? true; return _bestVisualStudioDetails[_isLaunchableKey] as bool? ?? true;
} }
/// True if the Visual Studio installation is as pre-release version. /// True if the Visual Studio installation is as pre-release version.
bool get isPrerelease => _bestVisualStudioDetails[_isPrereleaseKey] as bool ?? false; bool get isPrerelease => _bestVisualStudioDetails[_isPrereleaseKey] as bool? ?? false;
/// True if a reboot is required to complete the Visual Studio installation. /// True if a reboot is required to complete the Visual Studio installation.
bool get isRebootRequired => _bestVisualStudioDetails[_isRebootRequiredKey] as bool ?? false; bool get isRebootRequired => _bestVisualStudioDetails[_isRebootRequiredKey] as bool? ?? false;
/// The name of the recommended Visual Studio installer workload. /// The name of the recommended Visual Studio installer workload.
String get workloadDescription => 'Desktop development with C++'; String get workloadDescription => 'Desktop development with C++';
...@@ -106,8 +104,8 @@ class VisualStudio { ...@@ -106,8 +104,8 @@ class VisualStudio {
/// found. /// found.
/// ///
/// For instance: 10.0.18362.0. /// For instance: 10.0.18362.0.
String getWindows10SDKVersion() { String? getWindows10SDKVersion() {
final String sdkLocation = _getWindows10SdkLocation(); final String? sdkLocation = _getWindows10SdkLocation();
if (sdkLocation == null) { if (sdkLocation == null) {
return null; return null;
} }
...@@ -116,13 +114,13 @@ class VisualStudio { ...@@ -116,13 +114,13 @@ class VisualStudio {
return null; return null;
} }
// The directories in this folder are named by the SDK version. // The directories in this folder are named by the SDK version.
Version highestVersion; Version? highestVersion;
for (final FileSystemEntity versionEntry in sdkIncludeDirectory.listSync()) { for (final FileSystemEntity versionEntry in sdkIncludeDirectory.listSync()) {
if (versionEntry.basename.startsWith('10.')) { if (versionEntry.basename.startsWith('10.')) {
// Version only handles 3 components; strip off the '10.' to leave three // Version only handles 3 components; strip off the '10.' to leave three
// components, since they all start with that. // components, since they all start with that.
final Version version = Version.parse(versionEntry.basename.substring(3)); final Version? version = Version.parse(versionEntry.basename.substring(3));
if (highestVersion == null || version > highestVersion) { if (highestVersion == null || (version != null && version > highestVersion)) {
highestVersion = version; highestVersion = version;
} }
} }
...@@ -151,7 +149,7 @@ class VisualStudio { ...@@ -151,7 +149,7 @@ class VisualStudio {
/// The path to CMake, or null if no Visual Studio installation has /// The path to CMake, or null if no Visual Studio installation has
/// the components necessary to build. /// the components necessary to build.
String get cmakePath { String? get cmakePath {
final Map<String, dynamic> details = _usableVisualStudioDetails; final Map<String, dynamic> details = _usableVisualStudioDetails;
if (details.isEmpty) { if (details.isEmpty) {
return null; return null;
...@@ -170,7 +168,7 @@ class VisualStudio { ...@@ -170,7 +168,7 @@ class VisualStudio {
} }
/// The major version of the Visual Studio install, as an integer. /// The major version of the Visual Studio install, as an integer.
int get _majorVersion => fullVersion != null ? int.tryParse(fullVersion.split('.')[0]) : null; int? get _majorVersion => fullVersion != null ? int.tryParse(fullVersion.split('.')[0]) : null;
/// The path to vswhere.exe. /// The path to vswhere.exe.
/// ///
...@@ -178,12 +176,18 @@ class VisualStudio { ...@@ -178,12 +176,18 @@ class VisualStudio {
/// present then there isn't a new enough installation of VS. This path is /// present then there isn't a new enough installation of VS. This path is
/// not user-controllable, unlike the install location of Visual Studio /// not user-controllable, unlike the install location of Visual Studio
/// itself. /// itself.
String get _vswherePath => _fileSystem.path.join( String get _vswherePath {
_platform.environment['PROGRAMFILES(X86)'], const String programFilesEnv = 'PROGRAMFILES(X86)';
'Microsoft Visual Studio', if (!_platform.environment.containsKey(programFilesEnv)) {
'Installer', throwToolExit('%$programFilesEnv% environment variable not found.');
'vswhere.exe', }
); return _fileSystem.path.join(
_platform.environment[programFilesEnv]!,
'Microsoft Visual Studio',
'Installer',
'vswhere.exe',
);
}
/// Workload ID for use with vswhere requirements. /// Workload ID for use with vswhere requirements.
/// ///
...@@ -198,7 +202,7 @@ class VisualStudio { ...@@ -198,7 +202,7 @@ class VisualStudio {
/// ///
/// Maps from component IDs to description in the installer UI. /// Maps from component IDs to description in the installer UI.
/// See https://docs.microsoft.com/en-us/visualstudio/install/workload-and-component-ids /// See https://docs.microsoft.com/en-us/visualstudio/install/workload-and-component-ids
Map<String, String> _requiredComponents([int majorVersion]) { Map<String, String> _requiredComponents([int? majorVersion]) {
// The description of the C++ toolchain required by the template. The // The description of the C++ toolchain required by the template. The
// component name is significantly different in different versions. // component name is significantly different in different versions.
// When a new major version of VS is supported, its toolchain description // When a new major version of VS is supported, its toolchain description
...@@ -273,15 +277,17 @@ class VisualStudio { ...@@ -273,15 +277,17 @@ class VisualStudio {
/// Returns the details dictionary for the newest version of Visual Studio. /// Returns the details dictionary for the newest version of Visual Studio.
/// If [validateRequirements] is set, the search will be limited to versions /// If [validateRequirements] is set, the search will be limited to versions
/// that have all of the required workloads and components. /// that have all of the required workloads and components.
Map<String, dynamic> _visualStudioDetails({ Map<String, dynamic>? _visualStudioDetails({
bool validateRequirements = false, bool validateRequirements = false,
List<String> additionalArguments, List<String>? additionalArguments,
String requiredWorkload String? requiredWorkload
}) { }) {
final List<String> requirementArguments = validateRequirements final List<String> requirementArguments = validateRequirements
? <String>[ ? <String>[
'-requires', if (requiredWorkload != null) ...<String>[
requiredWorkload, '-requires',
requiredWorkload,
],
..._requiredComponents(_minimumSupportedVersion).keys ..._requiredComponents(_minimumSupportedVersion).keys
] ]
: <String>[]; : <String>[];
...@@ -296,7 +302,7 @@ class VisualStudio { ...@@ -296,7 +302,7 @@ class VisualStudio {
_vswherePath, _vswherePath,
...defaultArguments, ...defaultArguments,
...?additionalArguments, ...?additionalArguments,
...?requirementArguments, ...requirementArguments,
], encoding: utf8); ], encoding: utf8);
if (whereResult.exitCode == 0) { if (whereResult.exitCode == 0) {
final List<Map<String, dynamic>> installations = final List<Map<String, dynamic>> installations =
...@@ -342,16 +348,16 @@ class VisualStudio { ...@@ -342,16 +348,16 @@ class VisualStudio {
/// ///
/// If no installation is found, the cached VS details are set to an empty map /// If no installation is found, the cached VS details are set to an empty map
/// to avoid repeating vswhere queries that have already not found an installation. /// to avoid repeating vswhere queries that have already not found an installation.
Map<String, dynamic> _cachedUsableVisualStudioDetails; Map<String, dynamic>? _cachedUsableVisualStudioDetails;
Map<String, dynamic> get _usableVisualStudioDetails { Map<String, dynamic> get _usableVisualStudioDetails {
if (_cachedUsableVisualStudioDetails != null) { if (_cachedUsableVisualStudioDetails != null) {
return _cachedUsableVisualStudioDetails; return _cachedUsableVisualStudioDetails!;
} }
final List<String> minimumVersionArguments = <String>[ final List<String> minimumVersionArguments = <String>[
_vswhereMinVersionArgument, _vswhereMinVersionArgument,
_minimumSupportedVersion.toString(), _minimumSupportedVersion.toString(),
]; ];
Map<String, dynamic> visualStudioDetails; Map<String, dynamic>? visualStudioDetails;
// Check in the order of stable VS, stable BT, pre-release VS, pre-release BT // Check in the order of stable VS, stable BT, pre-release VS, pre-release BT
for (final bool checkForPrerelease in <bool>[false, true]) { for (final bool checkForPrerelease in <bool>[false, true]) {
for (final String requiredWorkload in _requiredWorkloads) { for (final String requiredWorkload in _requiredWorkloads) {
...@@ -372,7 +378,7 @@ class VisualStudio { ...@@ -372,7 +378,7 @@ class VisualStudio {
} }
} }
_cachedUsableVisualStudioDetails ??= <String, dynamic>{}; _cachedUsableVisualStudioDetails ??= <String, dynamic>{};
return _cachedUsableVisualStudioDetails; return _cachedUsableVisualStudioDetails!;
} }
/// Returns the details dictionary of the latest version of Visual Studio, /// Returns the details dictionary of the latest version of Visual Studio,
...@@ -382,14 +388,14 @@ class VisualStudio { ...@@ -382,14 +388,14 @@ class VisualStudio {
/// If no installation is found, the cached VS details are set to an empty map /// If no installation is found, the cached VS details are set to an empty map
/// to avoid repeating vswhere queries that have already not found an /// to avoid repeating vswhere queries that have already not found an
/// installation. /// installation.
Map<String, dynamic> _cachedAnyVisualStudioDetails; Map<String, dynamic>? _cachedAnyVisualStudioDetails;
Map<String, dynamic> get _anyVisualStudioDetails { Map<String, dynamic> get _anyVisualStudioDetails {
// Search for all types of installations. // Search for all types of installations.
_cachedAnyVisualStudioDetails ??= _visualStudioDetails( _cachedAnyVisualStudioDetails ??= _visualStudioDetails(
additionalArguments: <String>[_vswherePrereleaseArgument, '-all']); additionalArguments: <String>[_vswherePrereleaseArgument, '-all']);
// Add a sentinel empty value to avoid querying vswhere again. // Add a sentinel empty value to avoid querying vswhere again.
_cachedAnyVisualStudioDetails ??= <String, dynamic>{}; _cachedAnyVisualStudioDetails ??= <String, dynamic>{};
return _cachedAnyVisualStudioDetails; return _cachedAnyVisualStudioDetails!;
} }
/// Returns the details dictionary of the best available version of Visual /// Returns the details dictionary of the best available version of Visual
...@@ -406,7 +412,7 @@ class VisualStudio { ...@@ -406,7 +412,7 @@ class VisualStudio {
/// Returns the installation location of the Windows 10 SDKs, or null if the /// Returns the installation location of the Windows 10 SDKs, or null if the
/// registry doesn't contain that information. /// registry doesn't contain that information.
String _getWindows10SdkLocation() { String? _getWindows10SdkLocation() {
try { try {
final RunResult result = _processUtils.runSync(<String>[ final RunResult result = _processUtils.runSync(<String>[
'reg', 'reg',
...@@ -417,9 +423,9 @@ class VisualStudio { ...@@ -417,9 +423,9 @@ class VisualStudio {
]); ]);
if (result.exitCode == 0) { if (result.exitCode == 0) {
final RegExp pattern = RegExp(r'InstallationFolder\s+REG_SZ\s+(.+)'); final RegExp pattern = RegExp(r'InstallationFolder\s+REG_SZ\s+(.+)');
final RegExpMatch match = pattern.firstMatch(result.stdout); final RegExpMatch? match = pattern.firstMatch(result.stdout);
if (match != null) { if (match != null) {
return match.group(1).trim(); return match.group(1)!.trim();
} }
} }
} on ArgumentError { } on ArgumentError {
...@@ -434,21 +440,21 @@ class VisualStudio { ...@@ -434,21 +440,21 @@ class VisualStudio {
/// Windows 10 SDK installation directory. /// Windows 10 SDK installation directory.
/// ///
/// Returns null if no Windows 10 SDKs are found. /// Returns null if no Windows 10 SDKs are found.
String findHighestVersionInSdkDirectory(Directory dir) { String? findHighestVersionInSdkDirectory(Directory dir) {
// This contains subfolders that are named by the SDK version. // This contains subfolders that are named by the SDK version.
final Directory includeDir = dir.childDirectory('Includes'); final Directory includeDir = dir.childDirectory('Includes');
if (!includeDir.existsSync()) { if (!includeDir.existsSync()) {
return null; return null;
} }
Version highestVersion; Version? highestVersion;
for (final FileSystemEntity versionEntry in includeDir.listSync()) { for (final FileSystemEntity versionEntry in includeDir.listSync()) {
if (!versionEntry.basename.startsWith('10.')) { if (!versionEntry.basename.startsWith('10.')) {
continue; continue;
} }
// Version only handles 3 components; strip off the '10.' to leave three // Version only handles 3 components; strip off the '10.' to leave three
// components, since they all start with that. // components, since they all start with that.
final Version version = Version.parse(versionEntry.basename.substring(3)); final Version? version = Version.parse(versionEntry.basename.substring(3));
if (highestVersion == null || version > highestVersion) { if (highestVersion == null || (version != null && version > highestVersion)) {
highestVersion = version; highestVersion = version;
} }
} }
......
...@@ -342,6 +342,18 @@ void setNoViableToolchainInstallation( ...@@ -342,6 +342,18 @@ void setNoViableToolchainInstallation(
void main() { void main() {
group('Visual Studio', () { group('Visual Studio', () {
testWithoutContext('isInstalled throws when PROGRAMFILES(X86) env not set', () {
final VisualStudio visualStudio = VisualStudio(
logger: BufferLogger.test(),
fileSystem: MemoryFileSystem.test(style: FileSystemStyle.windows),
platform: FakePlatform(operatingSystem: 'windows'),
processManager: FakeProcessManager.any(),
);
expect(() => visualStudio.isInstalled,
throwsToolExit(message: '%PROGRAMFILES(X86)% environment variable not found'));
});
testWithoutContext('isInstalled and cmakePath correct when vswhere is missing', () { testWithoutContext('isInstalled and cmakePath correct when vswhere is missing', () {
final FileSystem fileSystem = MemoryFileSystem.test(style: FileSystemStyle.windows); final FileSystem fileSystem = MemoryFileSystem.test(style: FileSystemStyle.windows);
const Exception exception = ProcessException('vswhere', <String>[]); const Exception exception = ProcessException('vswhere', <String>[]);
......
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