diff --git a/pkgs/native_toolchain_c/CHANGELOG.md b/pkgs/native_toolchain_c/CHANGELOG.md index 1dae1eeb8b..798beb4849 100644 --- a/pkgs/native_toolchain_c/CHANGELOG.md +++ b/pkgs/native_toolchain_c/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.18.1 + +- Fix [native_toolchain_c failing with MSSSMS installed](https://github.com/dart-lang/native/issues/3327) by requiring vswhere to only show output that includes the necessary build tools + ## 0.18.0 - Made `CLinker.run` `Logger` argument optional. It now defaults to a logger diff --git a/pkgs/native_toolchain_c/lib/src/native_toolchain/msvc.dart b/pkgs/native_toolchain_c/lib/src/native_toolchain/msvc.dart index 8a1d20e2a5..b717cc55a4 100644 --- a/pkgs/native_toolchain_c/lib/src/native_toolchain/msvc.dart +++ b/pkgs/native_toolchain_c/lib/src/native_toolchain/msvc.dart @@ -38,7 +38,12 @@ final Tool vswhere = Tool( ), ); -/// Visual Studio. +/// A Visual Studio installation that ships the MSVC tools for x86/x64. +/// +/// Resolved via [vswhere] requiring +/// `Microsoft.VisualStudio.Component.VC.Tools.x86.x64`, which filters out +/// installs that lack the C++ toolchain (e.g. SSMS) even when their +/// `installationVersion` would otherwise win `vswhere -latest`. /// /// https://visualstudio.microsoft.com/ final Tool visualStudio = Tool( @@ -46,7 +51,22 @@ final Tool visualStudio = Tool( defaultResolver: VisualStudioResolver(), ); -/// The C/C++ Optimizing Compiler. +/// A Visual Studio installation that ships the MSVC tools for arm64. +/// +/// Resolved via [vswhere] requiring +/// `Microsoft.VisualStudio.Component.VC.Tools.arm64`. May resolve a +/// different installation than [visualStudio] when not every VS instance on +/// the machine has the arm64 cross toolchain installed. +/// +/// https://visualstudio.microsoft.com/ +final Tool visualStudioArm64 = Tool( + name: 'Visual Studio', + defaultResolver: VisualStudioResolverArm64(), +); + +/// The C/C++ Optimizing Compiler installation for targeting x86/x64. +/// +/// Resolved relative to [visualStudio]. final Tool msvc = Tool( name: 'MSVC', defaultResolver: PathVersionResolver( @@ -58,6 +78,22 @@ final Tool msvc = Tool( ), ); +/// The C/C++ Optimizing Compiler installation for targeting arm64. +/// +/// Resolved relative to [visualStudioArm64], which may point at a different +/// VS installation than [msvc] when only some installs have the arm64 +/// cross toolchain. +final Tool msvcArm64 = Tool( + name: 'MSVC', + defaultResolver: PathVersionResolver( + wrappedResolver: RelativeToolResolver( + toolName: 'MSVC', + wrappedResolver: visualStudioArm64.defaultResolver!, + relativePath: Uri(path: './VC/Tools/MSVC/*/'), + ), + ), +); + Tool vcvars(ToolInstance toolInstance) { final tool = toolInstance.tool; assert(tool == cl || tool == msvcLink || tool == lib); @@ -241,7 +277,10 @@ Tool _msvcTool({ final targetArchName = _msvcArchNames[targetArchitecture]!; ToolResolver resolver = RelativeToolResolver( toolName: executableName, - wrappedResolver: msvc.defaultResolver!, + wrappedResolver: switch (targetArchitecture) { + .arm64 => msvcArm64.defaultResolver!, + _ => msvc.defaultResolver!, + }, relativePath: Uri( path: 'bin/Host$hostArchName/$targetArchName/$executableName', ), @@ -256,9 +295,34 @@ Tool _msvcTool({ return Tool(name: executableName, defaultResolver: resolver); } +/// Resolves Visual Studio installations that ship the arm64 MSVC tools. +/// +/// Same as [VisualStudioResolver] but requires the +/// `Microsoft.VisualStudio.Component.VC.Tools.arm64` component. On machines +/// with multiple VS installations these resolvers may pick different +/// installs. +class VisualStudioResolverArm64 extends VisualStudioResolver { + @override + Future> resolve(ToolResolvingContext context) async => + resolveArch(context, 'arm64'); +} + +/// Resolves Visual Studio installations that ship the x86/x64 MSVC tools. +/// +/// Runs [vswhere] with `-latest -requires +/// Microsoft.VisualStudio.Component.VC.Tools.x86.x64`. The `-requires` +/// filter is necessary because tools like SSMS share the VS installer and +/// can outrank Visual Studio under `-latest` when sorted by version alone +/// (see https://github.com/dart-lang/native/issues/3327). class VisualStudioResolver implements ToolResolver { @override - Future> resolve(ToolResolvingContext context) async { + Future> resolve(ToolResolvingContext context) async => + resolveArch(context, 'x86.x64'); + + Future> resolveArch( + ToolResolvingContext context, + String pkgArchSuffix, + ) async { final vswhereInstances = await vswhere.defaultResolver!.resolve(context); final logger = context.logger; @@ -266,12 +330,40 @@ class VisualStudioResolver implements ToolResolver { for (final vswhereInstance in vswhereInstances.take(1)) { final vswhereResult = await runProcess( executable: vswhereInstance.uri, - arguments: ['-format', 'json', '-utf8', '-latest', '-products', '*'], + arguments: [ + '-format', + 'json', + '-utf8', + '-latest', + '-products', + '*', + '-requires', + 'Microsoft.VisualStudio.Component.VC.Tools.$pkgArchSuffix', + ], logger: logger, ); final instances = parseVswhere(vswhereResult.stdout, logger); result.addAll(instances); } + if (result.isEmpty) { + logger?.warning( + 'No Visual Studio installation found with the requested ' + 'Microsoft.VisualStudio.Component.VC.Tools.$pkgArchSuffix component.', + ); + logger?.info('You can install the missing package via'); + for (final vsWhere in vswhereInstances) { + final vsInstallerUri = vsWhere.uri + .resolve('../vs_installer.exe') + .toFilePath(); + logger?.warning( // highlight command for user + '` $vsInstallerUri install --add ', + 'Microsoft.VisualStudio.Component.VC.Tools.x86.x64`', + ); + if (vswhereInstances.length > 1 && vswhereInstances.last != vsWhere) { + logger?.info('or'); + } + } + } return result; } diff --git a/pkgs/native_toolchain_c/pubspec.yaml b/pkgs/native_toolchain_c/pubspec.yaml index e426ed34d9..c54f927161 100644 --- a/pkgs/native_toolchain_c/pubspec.yaml +++ b/pkgs/native_toolchain_c/pubspec.yaml @@ -1,7 +1,7 @@ name: native_toolchain_c description: >- A library to invoke the native C compiler installed on the host machine. -version: 0.18.0 +version: 0.18.1 repository: https://github.com/dart-lang/native/tree/main/pkgs/native_toolchain_c topics: diff --git a/pkgs/native_toolchain_c/test/native_toolchain/msvc_test.dart b/pkgs/native_toolchain_c/test/native_toolchain/msvc_test.dart index ce959ed606..4b2ebde38e 100644 --- a/pkgs/native_toolchain_c/test/native_toolchain/msvc_test.dart +++ b/pkgs/native_toolchain_c/test/native_toolchain/msvc_test.dart @@ -6,6 +6,7 @@ @OnPlatform({'windows': Timeout.factor(10)}) library; +import 'dart:convert'; import 'dart:io'; import 'package:native_toolchain_c/src/native_toolchain/msvc.dart'; @@ -37,19 +38,44 @@ void main() { expect(instances.isNotEmpty, true); }); - test('cl', () async { - final instances = await cl.defaultResolver!.resolve(systemContext); - expect(instances.isNotEmpty, true); - }); - - test('clIA32', () async { - final instances = await clIA32.defaultResolver!.resolve(systemContext); - expect(instances.isNotEmpty, true); - }); + test('parseVswhere handles mixed installations', () async { + final tempUri = await tempDirForTest(); + final ssmsDir = Directory.fromUri( + tempUri.resolve('Microsoft SQL Server Management Studio 22/Release'), + ); + final visualStudioDir = Directory.fromUri( + tempUri.resolve('Microsoft Visual Studio/18/Community'), + ); + await ssmsDir.create(recursive: true); + await visualStudioDir.create(recursive: true); + + final instances = VisualStudioResolver().parseVswhere( + jsonEncode([ + { + 'installationName': 'SSMS/22.5.0+11709.299', + 'installationPath': ssmsDir.path, + 'installationVersion': '22.5.11709.299', + 'productId': 'Microsoft.VisualStudio.Product.Ssms', + 'displayName': 'SQL Server Management Studio 22', + }, + { + 'installationName': 'VisualStudio/18.5.1+11716.220', + 'installationPath': visualStudioDir.path, + 'installationVersion': '18.5.11716.220', + 'productId': 'Microsoft.VisualStudio.Product.Community', + 'displayName': 'Visual Studio Community 2026', + }, + { + 'installationName': 'Incomplete entry', + 'productId': 'Microsoft.VisualStudio.Product.Incomplete', + }, + ]), + ); - test('clArm64', () async { - final instances = await clArm64.defaultResolver!.resolve(systemContext); - expect(instances.isNotEmpty, true); + expect(instances, hasLength(2)); + expect(instances.first.uri, ssmsDir.uri); + expect(instances.last.uri, visualStudioDir.uri); + expect(instances.map((instance) => instance.version!.major), [22, 18]); }); test('lib', () async {