diff --git a/packages/flutter_tools/lib/src/build_system/targets/native_assets.dart b/packages/flutter_tools/lib/src/build_system/targets/native_assets.dart index c92c1d694958..7a5ee2178800 100644 --- a/packages/flutter_tools/lib/src/build_system/targets/native_assets.dart +++ b/packages/flutter_tools/lib/src/build_system/targets/native_assets.dart @@ -16,6 +16,7 @@ import '../../linux/native_assets.dart'; import '../../macos/native_assets.dart'; import '../../macos/xcode.dart'; import '../../native_assets.dart'; +import '../../windows/native_assets.dart'; import '../build_system.dart'; import '../depfile.dart'; import '../exceptions.dart'; @@ -134,6 +135,20 @@ class NativeAssets extends Target { fileSystem: fileSystem, buildRunner: buildRunner, ); + case TargetPlatform.windows_x64: + final String? environmentBuildMode = environment.defines[kBuildMode]; + if (environmentBuildMode == null) { + throw MissingDefineException(kBuildMode, name); + } + final BuildMode buildMode = BuildMode.fromCliName(environmentBuildMode); + (_, dependencies) = await buildNativeAssetsWindows( + targetPlatform: targetPlatform, + buildMode: buildMode, + projectUri: projectUri, + yamlParentDirectory: environment.buildDir.uri, + fileSystem: fileSystem, + buildRunner: buildRunner, + ); case TargetPlatform.tester: if (const LocalPlatform().isMacOS) { (_, dependencies) = await buildNativeAssetsMacOS( @@ -154,6 +169,15 @@ class NativeAssets extends Target { buildRunner: buildRunner, flutterTester: true, ); + } else if (const LocalPlatform().isWindows) { + (_, dependencies) = await buildNativeAssetsWindows( + buildMode: BuildMode.debug, + projectUri: projectUri, + yamlParentDirectory: environment.buildDir.uri, + fileSystem: fileSystem, + buildRunner: buildRunner, + flutterTester: true, + ); } else { // TODO(dacoharkes): Implement other OSes. https://github.com/flutter/flutter/issues/129757 // Write the file we claim to have in the [outputs]. @@ -168,7 +192,6 @@ class NativeAssets extends Target { case TargetPlatform.fuchsia_arm64: case TargetPlatform.fuchsia_x64: case TargetPlatform.web_javascript: - case TargetPlatform.windows_x64: // TODO(dacoharkes): Implement other OSes. https://github.com/flutter/flutter/issues/129757 // Write the file we claim to have in the [outputs]. await writeNativeAssetsYaml([], environment.buildDir.uri, fileSystem); diff --git a/packages/flutter_tools/lib/src/ios/native_assets.dart b/packages/flutter_tools/lib/src/ios/native_assets.dart index 21ddb2c5e517..8a18b9eb1476 100644 --- a/packages/flutter_tools/lib/src/ios/native_assets.dart +++ b/packages/flutter_tools/lib/src/ios/native_assets.dart @@ -27,7 +27,7 @@ Future dryRunNativeAssetsIOS({ return null; } - final Uri buildUri_ = nativeAssetsBuildUri(projectUri, OS.iOS); + final Uri buildUri = nativeAssetsBuildUri(projectUri, OS.iOS); final Iterable assetTargetLocations = await dryRunNativeAssetsIOSInternal( fileSystem, projectUri, @@ -35,7 +35,7 @@ Future dryRunNativeAssetsIOS({ ); final Uri nativeAssetsUri = await writeNativeAssetsYaml( assetTargetLocations, - buildUri_, + buildUri, fileSystem, ); return nativeAssetsUri; @@ -46,17 +46,17 @@ Future> dryRunNativeAssetsIOSInternal( Uri projectUri, NativeAssetsBuildRunner buildRunner, ) async { - const OS targetOs = OS.iOS; - globals.logger.printTrace('Dry running native assets for $targetOs.'); + const OS targetOS = OS.iOS; + globals.logger.printTrace('Dry running native assets for $targetOS.'); final List nativeAssets = (await buildRunner.dryRun( linkModePreference: LinkModePreference.dynamic, - targetOs: targetOs, + targetOS: targetOS, workingDirectory: projectUri, includeParentEnvironment: true, )) .assets; ensureNoLinkModeStatic(nativeAssets); - globals.logger.printTrace('Dry running native assets for $targetOs done.'); + globals.logger.printTrace('Dry running native assets for $targetOS done.'); final Iterable assetTargetLocations = _assetTargetLocations(nativeAssets).values; return assetTargetLocations; } @@ -80,8 +80,8 @@ Future> buildNativeAssetsIOS({ final List targets = darwinArchs.map(_getNativeTarget).toList(); final native_assets_cli.BuildMode buildModeCli = nativeAssetsBuildMode(buildMode); - const OS targetOs = OS.iOS; - final Uri buildUri_ = nativeAssetsBuildUri(projectUri, targetOs); + const OS targetOS = OS.iOS; + final Uri buildUri = nativeAssetsBuildUri(projectUri, targetOS); final IOSSdk iosSdk = _getIOSSdk(environmentType); globals.logger.printTrace('Building native assets for $targets $buildModeCli.'); @@ -104,7 +104,7 @@ Future> buildNativeAssetsIOS({ globals.logger.printTrace('Building native assets for $targets done.'); final Map> fatAssetTargetLocations = _fatAssetTargetLocations(nativeAssets); await copyNativeAssetsMacOSHost( - buildUri_, + buildUri, fatAssetTargetLocations, codesignIdentity, buildMode, diff --git a/packages/flutter_tools/lib/src/linux/native_assets.dart b/packages/flutter_tools/lib/src/linux/native_assets.dart index 6c52a33707c3..692c4126ac83 100644 --- a/packages/flutter_tools/lib/src/linux/native_assets.dart +++ b/packages/flutter_tools/lib/src/linux/native_assets.dart @@ -2,9 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import 'package:native_assets_builder/native_assets_builder.dart' show BuildResult; import 'package:native_assets_cli/native_assets_cli.dart' hide BuildMode; -import 'package:native_assets_cli/native_assets_cli.dart' as native_assets_cli; import '../base/common.dart'; import '../base/file_system.dart'; @@ -22,24 +20,14 @@ Future dryRunNativeAssetsLinux({ required Uri projectUri, bool flutterTester = false, required FileSystem fileSystem, -}) async { - if (!await nativeBuildRequired(buildRunner)) { - return null; - } - - final Uri buildUri_ = nativeAssetsBuildUri(projectUri, OS.linux); - final Iterable nativeAssetPaths = await dryRunNativeAssetsLinuxInternal( - fileSystem, - projectUri, - flutterTester, - buildRunner, - ); - final Uri nativeAssetsUri = await writeNativeAssetsYaml( - nativeAssetPaths, - buildUri_, - fileSystem, +}) { + return dryRunNativeAssetsSingleArchitecture( + buildRunner: buildRunner, + projectUri: projectUri, + flutterTester: flutterTester, + fileSystem: fileSystem, + os: OS.linux, ); - return nativeAssetsUri; } Future> dryRunNativeAssetsLinuxInternal( @@ -47,33 +35,16 @@ Future> dryRunNativeAssetsLinuxInternal( Uri projectUri, bool flutterTester, NativeAssetsBuildRunner buildRunner, -) async { - const OS targetOs = OS.linux; - final Uri buildUri_ = nativeAssetsBuildUri(projectUri, targetOs); - - globals.logger.printTrace('Dry running native assets for $targetOs.'); - final List nativeAssets = (await buildRunner.dryRun( - linkModePreference: LinkModePreference.dynamic, - targetOs: targetOs, - workingDirectory: projectUri, - includeParentEnvironment: true, - )) - .assets; - ensureNoLinkModeStatic(nativeAssets); - globals.logger.printTrace('Dry running native assets for $targetOs done.'); - final Uri? absolutePath = flutterTester ? buildUri_ : null; - final Map assetTargetLocations = _assetTargetLocations(nativeAssets, absolutePath); - final Iterable nativeAssetPaths = assetTargetLocations.values; - return nativeAssetPaths; +) { + return dryRunNativeAssetsSingleArchitectureInternal( + fileSystem, + projectUri, + flutterTester, + buildRunner, + OS.linux, + ); } -/// Builds native assets. -/// -/// If [targetPlatform] is omitted, the current target architecture is used. -/// -/// If [flutterTester] is true, absolute paths are emitted in the native -/// assets mapping. This can be used for JIT mode without sandbox on the host. -/// This is used in `flutter test` and `flutter run -d flutter-tester`. Future<(Uri? nativeAssetsYaml, List dependencies)> buildNativeAssetsLinux({ required NativeAssetsBuildRunner buildRunner, TargetPlatform? targetPlatform, @@ -82,127 +53,16 @@ Future<(Uri? nativeAssetsYaml, List dependencies)> buildNativeAssetsLinux({ bool flutterTester = false, Uri? yamlParentDirectory, required FileSystem fileSystem, -}) async { - const OS targetOs = OS.linux; - final Uri buildUri_ = nativeAssetsBuildUri(projectUri, targetOs); - final Directory buildDir = fileSystem.directory(buildUri_); - if (!await buildDir.exists()) { - // CMake requires the folder to exist to do copying. - await buildDir.create(recursive: true); - } - if (!await nativeBuildRequired(buildRunner)) { - final Uri nativeAssetsYaml = await writeNativeAssetsYaml([], yamlParentDirectory ?? buildUri_, fileSystem); - return (nativeAssetsYaml, []); - } - - final Target target = targetPlatform != null ? _getNativeTarget(targetPlatform) : Target.current; - final native_assets_cli.BuildMode buildModeCli = nativeAssetsBuildMode(buildMode); - - globals.logger.printTrace('Building native assets for $target $buildModeCli.'); - final BuildResult result = await buildRunner.build( - linkModePreference: LinkModePreference.dynamic, - target: target, - buildMode: buildModeCli, - workingDirectory: projectUri, - includeParentEnvironment: true, - cCompilerConfig: await buildRunner.cCompilerConfig, - ); - final List nativeAssets = result.assets; - final Set dependencies = result.dependencies.toSet(); - ensureNoLinkModeStatic(nativeAssets); - globals.logger.printTrace('Building native assets for $target done.'); - final Uri? absolutePath = flutterTester ? buildUri_ : null; - final Map assetTargetLocations = _assetTargetLocations(nativeAssets, absolutePath); - await _copyNativeAssetsLinux( - buildUri_, - assetTargetLocations, - buildMode, - fileSystem, - ); - final Uri nativeAssetsUri = await writeNativeAssetsYaml( - assetTargetLocations.values, - yamlParentDirectory ?? buildUri_, - fileSystem, +}) { + return buildNativeAssetsSingleArchitecture( + buildRunner: buildRunner, + targetPlatform: targetPlatform, + projectUri: projectUri, + buildMode: buildMode, + flutterTester: flutterTester, + yamlParentDirectory: yamlParentDirectory, + fileSystem: fileSystem, ); - return (nativeAssetsUri, dependencies.toList()); -} - -Map _assetTargetLocations( - List nativeAssets, - Uri? absolutePath, -) => - { - for (final Asset asset in nativeAssets) asset: _targetLocationLinux(asset, absolutePath), - }; - -Asset _targetLocationLinux(Asset asset, Uri? absolutePath) { - final AssetPath path = asset.path; - switch (path) { - case AssetSystemPath _: - case AssetInExecutable _: - case AssetInProcess _: - return asset; - case AssetAbsolutePath _: - final String fileName = path.uri.pathSegments.last; - Uri uri; - if (absolutePath != null) { - // Flutter tester needs full host paths. - uri = absolutePath.resolve(fileName); - } else { - // Flutter Desktop needs "absolute" paths inside the app. - // "relative" in the context of native assets would be relative to the - // kernel or aot snapshot. - uri = Uri(path: fileName); - } - return asset.copyWith(path: AssetAbsolutePath(uri)); - } - throw Exception('Unsupported asset path type ${path.runtimeType} in asset $asset'); -} - -/// Extract the [Target] from a [TargetPlatform]. -Target _getNativeTarget(TargetPlatform targetPlatform) { - switch (targetPlatform) { - case TargetPlatform.linux_x64: - return Target.linuxX64; - case TargetPlatform.linux_arm64: - return Target.linuxArm64; - case TargetPlatform.android: - case TargetPlatform.ios: - case TargetPlatform.darwin: - case TargetPlatform.windows_x64: - case TargetPlatform.fuchsia_arm64: - case TargetPlatform.fuchsia_x64: - case TargetPlatform.tester: - case TargetPlatform.web_javascript: - case TargetPlatform.android_arm: - case TargetPlatform.android_arm64: - case TargetPlatform.android_x64: - case TargetPlatform.android_x86: - throw Exception('Unknown targetPlatform: $targetPlatform.'); - } -} - -Future _copyNativeAssetsLinux( - Uri buildUri, - Map assetTargetLocations, - BuildMode buildMode, - FileSystem fileSystem, -) async { - if (assetTargetLocations.isNotEmpty) { - globals.logger.printTrace('Copying native assets to ${buildUri.toFilePath()}.'); - final Directory buildDir = fileSystem.directory(buildUri.toFilePath()); - if (!buildDir.existsSync()) { - buildDir.createSync(recursive: true); - } - for (final MapEntry assetMapping in assetTargetLocations.entries) { - final Uri source = (assetMapping.key.path as AssetAbsolutePath).uri; - final Uri target = (assetMapping.value.path as AssetAbsolutePath).uri; - final Uri targetUri = buildUri.resolveUri(target); - final String targetFullPath = targetUri.toFilePath(); - await fileSystem.file(source).copy(targetFullPath); - } - globals.logger.printTrace('Copying native assets done.'); - } } /// Flutter expects `clang++` to be on the path on Linux hosts. diff --git a/packages/flutter_tools/lib/src/macos/native_assets.dart b/packages/flutter_tools/lib/src/macos/native_assets.dart index 1bdf27d76ffa..5e79097f2ca0 100644 --- a/packages/flutter_tools/lib/src/macos/native_assets.dart +++ b/packages/flutter_tools/lib/src/macos/native_assets.dart @@ -27,9 +27,9 @@ Future dryRunNativeAssetsMacOS({ return null; } - final Uri buildUri_ = nativeAssetsBuildUri(projectUri, OS.macOS); + final Uri buildUri = nativeAssetsBuildUri(projectUri, OS.macOS); final Iterable nativeAssetPaths = await dryRunNativeAssetsMacOSInternal(fileSystem, projectUri, flutterTester, buildRunner); - final Uri nativeAssetsUri = await writeNativeAssetsYaml(nativeAssetPaths, buildUri_, fileSystem); + final Uri nativeAssetsUri = await writeNativeAssetsYaml(nativeAssetPaths, buildUri, fileSystem); return nativeAssetsUri; } @@ -39,20 +39,20 @@ Future> dryRunNativeAssetsMacOSInternal( bool flutterTester, NativeAssetsBuildRunner buildRunner, ) async { - const OS targetOs = OS.macOS; - final Uri buildUri_ = nativeAssetsBuildUri(projectUri, targetOs); + const OS targetOS = OS.macOS; + final Uri buildUri = nativeAssetsBuildUri(projectUri, targetOS); - globals.logger.printTrace('Dry running native assets for $targetOs.'); + globals.logger.printTrace('Dry running native assets for $targetOS.'); final List nativeAssets = (await buildRunner.dryRun( linkModePreference: LinkModePreference.dynamic, - targetOs: targetOs, + targetOS: targetOS, workingDirectory: projectUri, includeParentEnvironment: true, )) .assets; ensureNoLinkModeStatic(nativeAssets); - globals.logger.printTrace('Dry running native assets for $targetOs done.'); - final Uri? absolutePath = flutterTester ? buildUri_ : null; + globals.logger.printTrace('Dry running native assets for $targetOS done.'); + final Uri? absolutePath = flutterTester ? buildUri : null; final Map assetTargetLocations = _assetTargetLocations(nativeAssets, absolutePath); final Iterable nativeAssetPaths = assetTargetLocations.values; return nativeAssetPaths; @@ -75,10 +75,10 @@ Future<(Uri? nativeAssetsYaml, List dependencies)> buildNativeAssetsMacOS({ Uri? yamlParentDirectory, required FileSystem fileSystem, }) async { - const OS targetOs = OS.macOS; - final Uri buildUri_ = nativeAssetsBuildUri(projectUri, targetOs); + const OS targetOS = OS.macOS; + final Uri buildUri = nativeAssetsBuildUri(projectUri, targetOS); if (!await nativeBuildRequired(buildRunner)) { - final Uri nativeAssetsYaml = await writeNativeAssetsYaml([], yamlParentDirectory ?? buildUri_, fileSystem); + final Uri nativeAssetsYaml = await writeNativeAssetsYaml([], yamlParentDirectory ?? buildUri, fileSystem); return (nativeAssetsYaml, []); } @@ -102,11 +102,11 @@ Future<(Uri? nativeAssetsYaml, List dependencies)> buildNativeAssetsMacOS({ } ensureNoLinkModeStatic(nativeAssets); globals.logger.printTrace('Building native assets for $targets done.'); - final Uri? absolutePath = flutterTester ? buildUri_ : null; + final Uri? absolutePath = flutterTester ? buildUri : null; final Map assetTargetLocations = _assetTargetLocations(nativeAssets, absolutePath); final Map> fatAssetTargetLocations = _fatAssetTargetLocations(nativeAssets, absolutePath); - await copyNativeAssetsMacOSHost(buildUri_, fatAssetTargetLocations, codesignIdentity, buildMode, fileSystem); - final Uri nativeAssetsUri = await writeNativeAssetsYaml(assetTargetLocations.values, yamlParentDirectory ?? buildUri_, fileSystem); + await copyNativeAssetsMacOSHost(buildUri, fatAssetTargetLocations, codesignIdentity, buildMode, fileSystem); + final Uri nativeAssetsUri = await writeNativeAssetsYaml(assetTargetLocations.values, yamlParentDirectory ?? buildUri, fileSystem); return (nativeAssetsUri, dependencies.toList()); } diff --git a/packages/flutter_tools/lib/src/migrations/cmake_native_assets_migration.dart b/packages/flutter_tools/lib/src/migrations/cmake_native_assets_migration.dart index fe9ba86a035a..c7f21351afbb 100644 --- a/packages/flutter_tools/lib/src/migrations/cmake_native_assets_migration.dart +++ b/packages/flutter_tools/lib/src/migrations/cmake_native_assets_migration.dart @@ -41,8 +41,8 @@ class CmakeNativeAssetsMigration extends ProjectMigrator { # Copy the native assets provided by the build.dart from all packages. set(NATIVE_ASSETS_DIR "\${PROJECT_BUILD_DIR}native_assets/$os/") install(DIRECTORY "\${NATIVE_ASSETS_DIR}" - DESTINATION "\${INSTALL_BUNDLE_LIB_DIR}" - COMPONENT Runtime) + DESTINATION "\${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) '''; // Insert the new command after the bundled libraries loop. diff --git a/packages/flutter_tools/lib/src/native_assets.dart b/packages/flutter_tools/lib/src/native_assets.dart index caeffa356333..38afb4e6e60d 100644 --- a/packages/flutter_tools/lib/src/native_assets.dart +++ b/packages/flutter_tools/lib/src/native_assets.dart @@ -23,6 +23,7 @@ import 'linux/native_assets.dart'; import 'macos/native_assets.dart'; import 'macos/native_assets_host.dart'; import 'resident_runner.dart'; +import 'windows/native_assets.dart'; /// Programmatic API to be used by Dart launchers to invoke native builds. /// @@ -42,7 +43,7 @@ abstract class NativeAssetsBuildRunner { Future dryRun({ required bool includeParentEnvironment, required LinkModePreference linkModePreference, - required OS targetOs, + required OS targetOS, required Uri workingDirectory, }); @@ -100,10 +101,8 @@ class NativeAssetsBuildRunnerImpl implements NativeAssetsBuildRunner { @override Future hasPackageConfig() { - final File packageConfigJson = fileSystem - .directory(projectUri.toFilePath()) - .childDirectory('.dart_tool') - .childFile('package_config.json'); + final File packageConfigJson = + fileSystem.directory(projectUri.toFilePath()).childDirectory('.dart_tool').childFile('package_config.json'); return packageConfigJson.exists(); } @@ -120,7 +119,7 @@ class NativeAssetsBuildRunnerImpl implements NativeAssetsBuildRunner { Future dryRun({ required bool includeParentEnvironment, required LinkModePreference linkModePreference, - required OS targetOs, + required OS targetOS, required Uri workingDirectory, }) { final PackageLayout packageLayout = PackageLayout.fromPackageConfig( @@ -130,7 +129,7 @@ class NativeAssetsBuildRunnerImpl implements NativeAssetsBuildRunner { return _buildRunner.dryRun( includeParentEnvironment: includeParentEnvironment, linkModePreference: linkModePreference, - targetOs: targetOs, + targetOs: targetOS, workingDirectory: workingDirectory, packageLayout: packageLayout, ); @@ -172,8 +171,11 @@ class NativeAssetsBuildRunnerImpl implements NativeAssetsBuildRunner { if (globals.platform.isLinux) { return cCompilerConfigLinux(); } + if (globals.platform.isWindows) { + return cCompilerConfigWindows(); + } throwToolExit( - 'Native assets feature not yet implemented for Linux, Windows and Android.', + 'Native assets feature not yet implemented for Android.', ); }(); } @@ -343,6 +345,13 @@ Future dryRunNativeAssets({ fileSystem: fileSystem, buildRunner: buildRunner, ); + } else if (const LocalPlatform().isWindows) { + nativeAssetsYaml = await dryRunNativeAssetsWindows( + projectUri: projectUri, + flutterTester: true, + fileSystem: fileSystem, + buildRunner: buildRunner, + ); } else { await nativeBuildRequired(buildRunner); nativeAssetsYaml = null; @@ -354,6 +363,12 @@ Future dryRunNativeAssets({ fileSystem: fileSystem, buildRunner: buildRunner, ); + case build_info.TargetPlatform.windows_x64: + nativeAssetsYaml = await dryRunNativeAssetsWindows( + projectUri: projectUri, + fileSystem: fileSystem, + buildRunner: buildRunner, + ); case build_info.TargetPlatform.android_arm: case build_info.TargetPlatform.android_arm64: case build_info.TargetPlatform.android_x64: @@ -362,7 +377,6 @@ Future dryRunNativeAssets({ case build_info.TargetPlatform.fuchsia_arm64: case build_info.TargetPlatform.fuchsia_x64: case build_info.TargetPlatform.web_javascript: - case build_info.TargetPlatform.windows_x64: await ensureNoNativeAssetsOrOsIsSupported( projectUri, targetPlatform.toString(), @@ -387,19 +401,41 @@ Future dryRunNativeAssetsMultipeOSes({ return null; } - final Uri buildUri_ = buildUriMultiple(projectUri); + final Uri buildUri = buildUriMultiple(projectUri); final Iterable nativeAssetPaths = [ if (targetPlatforms.contains(build_info.TargetPlatform.darwin) || (targetPlatforms.contains(build_info.TargetPlatform.tester) && OS.current == OS.macOS)) - ...await dryRunNativeAssetsMacOSInternal(fileSystem, projectUri, false, buildRunner), + ...await dryRunNativeAssetsMacOSInternal( + fileSystem, + projectUri, + false, + buildRunner, + ), if (targetPlatforms.contains(build_info.TargetPlatform.linux_arm64) || targetPlatforms.contains(build_info.TargetPlatform.linux_x64) || (targetPlatforms.contains(build_info.TargetPlatform.tester) && OS.current == OS.linux)) - ...await dryRunNativeAssetsLinuxInternal(fileSystem, projectUri, false, buildRunner), + ...await dryRunNativeAssetsLinuxInternal( + fileSystem, + projectUri, + false, + buildRunner, + ), + if (targetPlatforms.contains(build_info.TargetPlatform.windows_x64) || + (targetPlatforms.contains(build_info.TargetPlatform.tester) && OS.current == OS.windows)) + ...await dryRunNativeAssetsWindowsInternal( + fileSystem, + projectUri, + false, + buildRunner, + ), if (targetPlatforms.contains(build_info.TargetPlatform.ios)) - ...await dryRunNativeAssetsIOSInternal(fileSystem, projectUri, buildRunner) + ...await dryRunNativeAssetsIOSInternal( + fileSystem, + projectUri, + buildRunner, + ) ]; - final Uri nativeAssetsUri = await writeNativeAssetsYaml(nativeAssetPaths, buildUri_, fileSystem); + final Uri nativeAssetsUri = await writeNativeAssetsYaml(nativeAssetPaths, buildUri, fileSystem); return nativeAssetsUri; } @@ -409,3 +445,213 @@ Uri buildUriMultiple(Uri projectUri) { final String buildDir = build_info.getBuildDirectory(); return projectUri.resolve('$buildDir/native_assets/multiple/'); } + +/// Dry run the native builds. +/// +/// This does not build native assets, it only simulates what the final paths +/// of all assets will be so that this can be embedded in the kernel file. +Future dryRunNativeAssetsSingleArchitecture({ + required NativeAssetsBuildRunner buildRunner, + required Uri projectUri, + bool flutterTester = false, + required FileSystem fileSystem, + required OS os, +}) async { + if (!await nativeBuildRequired(buildRunner)) { + return null; + } + + final Uri buildUri = nativeAssetsBuildUri(projectUri, os); + final Iterable nativeAssetPaths = await dryRunNativeAssetsSingleArchitectureInternal( + fileSystem, + projectUri, + flutterTester, + buildRunner, + os, + ); + final Uri nativeAssetsUri = await writeNativeAssetsYaml( + nativeAssetPaths, + buildUri, + fileSystem, + ); + return nativeAssetsUri; +} + +Future> dryRunNativeAssetsSingleArchitectureInternal( + FileSystem fileSystem, + Uri projectUri, + bool flutterTester, + NativeAssetsBuildRunner buildRunner, + OS targetOS, +) async { + final Uri buildUri = nativeAssetsBuildUri(projectUri, targetOS); + + globals.logger.printTrace('Dry running native assets for $targetOS.'); + final List nativeAssets = (await buildRunner.dryRun( + linkModePreference: LinkModePreference.dynamic, + targetOS: targetOS, + workingDirectory: projectUri, + includeParentEnvironment: true, + )) + .assets; + ensureNoLinkModeStatic(nativeAssets); + globals.logger.printTrace('Dry running native assets for $targetOS done.'); + final Uri? absolutePath = flutterTester ? buildUri : null; + final Map assetTargetLocations = _assetTargetLocationsSingleArchitecture( + nativeAssets, + absolutePath, + ); + final Iterable nativeAssetPaths = assetTargetLocations.values; + return nativeAssetPaths; +} + +/// Builds native assets. +/// +/// If [targetPlatform] is omitted, the current target architecture is used. +/// +/// If [flutterTester] is true, absolute paths are emitted in the native +/// assets mapping. This can be used for JIT mode without sandbox on the host. +/// This is used in `flutter test` and `flutter run -d flutter-tester`. +Future<(Uri? nativeAssetsYaml, List dependencies)> buildNativeAssetsSingleArchitecture({ + required NativeAssetsBuildRunner buildRunner, + build_info.TargetPlatform? targetPlatform, + required Uri projectUri, + required build_info.BuildMode buildMode, + bool flutterTester = false, + Uri? yamlParentDirectory, + required FileSystem fileSystem, +}) async { + final Target target = targetPlatform != null ? _getNativeTarget(targetPlatform) : Target.current; + final OS targetOS = target.os; + final Uri buildUri = nativeAssetsBuildUri(projectUri, targetOS); + final Directory buildDir = fileSystem.directory(buildUri); + if (!await buildDir.exists()) { + // CMake requires the folder to exist to do copying. + await buildDir.create(recursive: true); + } + if (!await nativeBuildRequired(buildRunner)) { + final Uri nativeAssetsYaml = await writeNativeAssetsYaml( + [], + yamlParentDirectory ?? buildUri, + fileSystem, + ); + return (nativeAssetsYaml, []); + } + + final BuildMode buildModeCli = nativeAssetsBuildMode(buildMode); + + globals.logger.printTrace('Building native assets for $target $buildModeCli.'); + final BuildResult result = await buildRunner.build( + linkModePreference: LinkModePreference.dynamic, + target: target, + buildMode: buildModeCli, + workingDirectory: projectUri, + includeParentEnvironment: true, + cCompilerConfig: await buildRunner.cCompilerConfig, + ); + final List nativeAssets = result.assets; + final Set dependencies = result.dependencies.toSet(); + ensureNoLinkModeStatic(nativeAssets); + globals.logger.printTrace('Building native assets for $target done.'); + final Uri? absolutePath = flutterTester ? buildUri : null; + final Map assetTargetLocations = _assetTargetLocationsSingleArchitecture(nativeAssets, absolutePath); + await _copyNativeAssetsSingleArchitecture( + buildUri, + assetTargetLocations, + buildMode, + fileSystem, + ); + final Uri nativeAssetsUri = await writeNativeAssetsYaml( + assetTargetLocations.values, + yamlParentDirectory ?? buildUri, + fileSystem, + ); + return (nativeAssetsUri, dependencies.toList()); +} + +Map _assetTargetLocationsSingleArchitecture( + List nativeAssets, + Uri? absolutePath, +) { + return { + for (final Asset asset in nativeAssets) + asset: _targetLocationSingleArchitecture( + asset, + absolutePath, + ), + }; +} + +Asset _targetLocationSingleArchitecture(Asset asset, Uri? absolutePath) { + final AssetPath path = asset.path; + switch (path) { + case AssetSystemPath _: + case AssetInExecutable _: + case AssetInProcess _: + return asset; + case AssetAbsolutePath _: + final String fileName = path.uri.pathSegments.last; + Uri uri; + if (absolutePath != null) { + // Flutter tester needs full host paths. + uri = absolutePath.resolve(fileName); + } else { + // Flutter Desktop needs "absolute" paths inside the app. + // "relative" in the context of native assets would be relative to the + // kernel or aot snapshot. + uri = Uri(path: fileName); + } + return asset.copyWith(path: AssetAbsolutePath(uri)); + } + throw Exception('Unsupported asset path type ${path.runtimeType} in asset $asset'); +} + +/// Extract the [Target] from a [TargetPlatform]. +/// +/// Does not cover MacOS, iOS, and Android as these pass the architecture +/// in other enums. +Target _getNativeTarget(build_info.TargetPlatform targetPlatform) { + switch (targetPlatform) { + case build_info.TargetPlatform.linux_x64: + return Target.linuxX64; + case build_info.TargetPlatform.linux_arm64: + return Target.linuxArm64; + case build_info.TargetPlatform.windows_x64: + return Target.windowsX64; + case build_info.TargetPlatform.android: + case build_info.TargetPlatform.ios: + case build_info.TargetPlatform.darwin: + case build_info.TargetPlatform.fuchsia_arm64: + case build_info.TargetPlatform.fuchsia_x64: + case build_info.TargetPlatform.tester: + case build_info.TargetPlatform.web_javascript: + case build_info.TargetPlatform.android_arm: + case build_info.TargetPlatform.android_arm64: + case build_info.TargetPlatform.android_x64: + case build_info.TargetPlatform.android_x86: + throw Exception('Unknown targetPlatform: $targetPlatform.'); + } +} + +Future _copyNativeAssetsSingleArchitecture( + Uri buildUri, + Map assetTargetLocations, + build_info.BuildMode buildMode, + FileSystem fileSystem, +) async { + if (assetTargetLocations.isNotEmpty) { + globals.logger.printTrace('Copying native assets to ${buildUri.toFilePath()}.'); + final Directory buildDir = fileSystem.directory(buildUri.toFilePath()); + if (!buildDir.existsSync()) { + buildDir.createSync(recursive: true); + } + for (final MapEntry assetMapping in assetTargetLocations.entries) { + final Uri source = (assetMapping.key.path as AssetAbsolutePath).uri; + final Uri target = (assetMapping.value.path as AssetAbsolutePath).uri; + final Uri targetUri = buildUri.resolveUri(target); + final String targetFullPath = targetUri.toFilePath(); + await fileSystem.file(source).copy(targetFullPath); + } + globals.logger.printTrace('Copying native assets done.'); + } +} diff --git a/packages/flutter_tools/lib/src/test/test_compiler.dart b/packages/flutter_tools/lib/src/test/test_compiler.dart index 600478faa3e2..1433a7150c26 100644 --- a/packages/flutter_tools/lib/src/test/test_compiler.dart +++ b/packages/flutter_tools/lib/src/test/test_compiler.dart @@ -20,6 +20,7 @@ import '../linux/native_assets.dart'; import '../macos/native_assets.dart'; import '../native_assets.dart'; import '../project.dart'; +import '../windows/native_assets.dart'; import 'test_time_recorder.dart'; /// A request to the [TestCompiler] for recompilation. @@ -195,6 +196,14 @@ class TestCompiler { fileSystem: globals.fs, buildRunner: buildRunner, ); + } else if (globals.platform.isWindows) { + (nativeAssetsYaml, _) = await buildNativeAssetsWindows( + buildMode: buildInfo.mode, + projectUri: projectUri, + flutterTester: true, + fileSystem: globals.fs, + buildRunner: buildRunner, + ); } else { await ensureNoNativeAssetsOrOsIsSupported( projectUri, diff --git a/packages/flutter_tools/lib/src/windows/build_windows.dart b/packages/flutter_tools/lib/src/windows/build_windows.dart index 5d88dddef5fe..dddc0d424d2b 100644 --- a/packages/flutter_tools/lib/src/windows/build_windows.dart +++ b/packages/flutter_tools/lib/src/windows/build_windows.dart @@ -18,6 +18,7 @@ import '../convert.dart'; import '../flutter_plugins.dart'; import '../globals.dart' as globals; import '../migrations/cmake_custom_command_migration.dart'; +import '../migrations/cmake_native_assets_migration.dart'; import 'migrations/build_architecture_migration.dart'; import 'migrations/show_window_migration.dart'; import 'migrations/version_migration.dart'; @@ -61,6 +62,7 @@ Future buildWindows(WindowsProject windowsProject, BuildInfo buildInfo, { final List migrators = [ CmakeCustomCommandMigration(windowsProject, globals.logger), + CmakeNativeAssetsMigration(windowsProject, 'windows', globals.logger), VersionMigration(windowsProject, globals.logger), ShowWindowMigration(windowsProject, globals.logger), BuildArchitectureMigration(windowsProject, buildDirectory, globals.logger), diff --git a/packages/flutter_tools/lib/src/windows/native_assets.dart b/packages/flutter_tools/lib/src/windows/native_assets.dart new file mode 100644 index 000000000000..c5f5835d0084 --- /dev/null +++ b/packages/flutter_tools/lib/src/windows/native_assets.dart @@ -0,0 +1,91 @@ +// Copyright 2014 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:native_assets_cli/native_assets_cli.dart' hide BuildMode; + +import '../base/file_system.dart'; +import '../build_info.dart'; +import '../globals.dart' as globals; +import '../native_assets.dart'; +import 'visual_studio.dart'; + +/// Dry run the native builds. +/// +/// This does not build native assets, it only simulates what the final paths +/// of all assets will be so that this can be embedded in the kernel file. +Future dryRunNativeAssetsWindows({ + required NativeAssetsBuildRunner buildRunner, + required Uri projectUri, + bool flutterTester = false, + required FileSystem fileSystem, +}) { + return dryRunNativeAssetsSingleArchitecture( + buildRunner: buildRunner, + projectUri: projectUri, + flutterTester: flutterTester, + fileSystem: fileSystem, + os: OS.windows, + ); +} + +Future> dryRunNativeAssetsWindowsInternal( + FileSystem fileSystem, + Uri projectUri, + bool flutterTester, + NativeAssetsBuildRunner buildRunner, +) { + return dryRunNativeAssetsSingleArchitectureInternal( + fileSystem, + projectUri, + flutterTester, + buildRunner, + OS.windows, + ); +} + +Future<(Uri? nativeAssetsYaml, List dependencies)> + buildNativeAssetsWindows({ + required NativeAssetsBuildRunner buildRunner, + TargetPlatform? targetPlatform, + required Uri projectUri, + required BuildMode buildMode, + bool flutterTester = false, + Uri? yamlParentDirectory, + required FileSystem fileSystem, +}) { + return buildNativeAssetsSingleArchitecture( + buildRunner: buildRunner, + targetPlatform: targetPlatform, + projectUri: projectUri, + buildMode: buildMode, + flutterTester: flutterTester, + yamlParentDirectory: yamlParentDirectory, + fileSystem: fileSystem, + ); +} + + +Future cCompilerConfigWindows() async { + final VisualStudio visualStudio = VisualStudio( + fileSystem: globals.fs, + platform: globals.platform, + logger: globals.logger, + processManager: globals.processManager, + ); + + return CCompilerConfig( + cc: _toOptionalFileUri(visualStudio.clPath), + ld: _toOptionalFileUri(visualStudio.linkPath), + ar: _toOptionalFileUri(visualStudio.libPath), + envScript: _toOptionalFileUri(visualStudio.vcvarsPath), + envScriptArgs: [], + ); +} + +Uri? _toOptionalFileUri(String? string) { + if (string == null) { + return null; + } + return Uri.file(string); +} diff --git a/packages/flutter_tools/lib/src/windows/visual_studio.dart b/packages/flutter_tools/lib/src/windows/visual_studio.dart index e9c39aa2fa4d..5251450a81b4 100644 --- a/packages/flutter_tools/lib/src/windows/visual_studio.dart +++ b/packages/flutter_tools/lib/src/windows/visual_studio.dart @@ -184,6 +184,60 @@ class VisualStudio { } } + /// The path to cl.exe, or null if no Visual Studio installation has + /// the components necessary to build. + String? get clPath { + return _getMsvcBinPath('cl.exe'); + } + + /// The path to lib.exe, or null if no Visual Studio installation has + /// the components necessary to build. + String? get libPath { + return _getMsvcBinPath('lib.exe'); + } + + /// The path to link.exe, or null if no Visual Studio installation has + /// the components necessary to build. + String? get linkPath { + return _getMsvcBinPath('link.exe'); + } + + String? _getMsvcBinPath(String executable) { + final VswhereDetails? details = _bestVisualStudioDetails; + if (details == null || !details.isUsable || details.installationPath == null || details.msvcVersion == null) { + return null; + } + + return _fileSystem.path.joinAll([ + details.installationPath!, + 'VC', + 'Tools', + 'MSVC', + details.msvcVersion!, + 'bin', + 'Hostx64', + 'x64', + executable, + ]); + } + + /// The path to vcvars64.exe, or null if no Visual Studio installation has + /// the components necessary to build. + String? get vcvarsPath { + final VswhereDetails? details = _bestVisualStudioDetails; + if (details == null || !details.isUsable || details.installationPath == null) { + return null; + } + + return _fileSystem.path.joinAll([ + details.installationPath!, + 'VC', + 'Auxiliary', + 'Build', + 'vcvars64.bat', + ]); + } + /// The major version of the Visual Studio install, as an integer. int? get _majorVersion => fullVersion != null ? int.tryParse(fullVersion!.split('.')[0]) : null; @@ -301,7 +355,12 @@ class VisualStudio { if (whereResult.exitCode == 0) { final List>? installations = _tryDecodeVswhereJson(whereResult.stdout); if (installations != null && installations.isNotEmpty) { - return VswhereDetails.fromJson(validateRequirements, installations[0]); + final String? msvcVersion = _findMsvcVersion(installations); + return VswhereDetails.fromJson( + validateRequirements, + installations[0], + msvcVersion, + ); } } } on ArgumentError { @@ -312,6 +371,28 @@ class VisualStudio { return null; } + String? _findMsvcVersion(List> installations) { + final String? installationPath = installations[0]['installationPath'] as String?; + String? msvcVersion; + if (installationPath != null) { + final Directory installationDir = _fileSystem.directory(installationPath); + final Directory msvcDir = installationDir + .childDirectory('VC') + .childDirectory('Tools') + .childDirectory('MSVC'); + if (msvcDir.existsSync()) { + final Iterable msvcVersionDirs = msvcDir.listSync().whereType(); + if (msvcVersionDirs.isEmpty) { + return null; + } + msvcVersion = msvcVersionDirs.last.uri.pathSegments + .where((String e) => e.isNotEmpty) + .last; + } + } + return msvcVersion; + } + List>? _tryDecodeVswhereJson(String vswhereJson) { List? result; FormatException? originalError; @@ -443,12 +524,14 @@ class VswhereDetails { required this.isRebootRequired, required this.isPrerelease, required this.catalogDisplayVersion, + required this.msvcVersion, }); /// Create a `VswhereDetails` from the JSON output of vswhere.exe. factory VswhereDetails.fromJson( bool meetsRequirements, - Map details + Map details, + String? msvcVersion, ) { final Map? catalog = details['catalog'] as Map?; @@ -467,6 +550,8 @@ class VswhereDetails { // contain replacement characters. displayName: details['displayName'] as String?, catalogDisplayVersion: catalog == null ? null : catalog['productDisplayVersion'] as String?, + + msvcVersion: msvcVersion, ); } @@ -511,6 +596,9 @@ class VswhereDetails { /// The user-friendly version. final String? catalogDisplayVersion; + /// The MSVC versions. + final String? msvcVersion; + /// Checks if the Visual Studio installation can be used by Flutter. /// /// Returns false if the installation has issues the user must resolve. diff --git a/packages/flutter_tools/templates/app_shared/windows.tmpl/CMakeLists.txt.tmpl b/packages/flutter_tools/templates/app_shared/windows.tmpl/CMakeLists.txt.tmpl index 78f98b83d572..0c89cfcd82fe 100644 --- a/packages/flutter_tools/templates/app_shared/windows.tmpl/CMakeLists.txt.tmpl +++ b/packages/flutter_tools/templates/app_shared/windows.tmpl/CMakeLists.txt.tmpl @@ -91,6 +91,12 @@ if(PLUGIN_BUNDLED_LIBRARIES) COMPONENT Runtime) endif() +# Copy the native assets provided by the build.dart from all packages. +set(NATIVE_ASSETS_DIR "${PROJECT_BUILD_DIR}native_assets/windows/") +install(DIRECTORY "${NATIVE_ASSETS_DIR}" + DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) + # Fully re-copy the assets directory on each build to avoid having stale files # from a previous install. set(FLUTTER_ASSET_DIR_NAME "flutter_assets") diff --git a/packages/flutter_tools/templates/package_ffi/pubspec.yaml.tmpl b/packages/flutter_tools/templates/package_ffi/pubspec.yaml.tmpl index 8fa5f3ac67ad..9237f30b9719 100644 --- a/packages/flutter_tools/templates/package_ffi/pubspec.yaml.tmpl +++ b/packages/flutter_tools/templates/package_ffi/pubspec.yaml.tmpl @@ -10,7 +10,7 @@ dependencies: cli_config: ^0.1.1 logging: ^1.1.1 native_assets_cli: ^0.2.0 - native_toolchain_c: ^0.2.0 + native_toolchain_c: ^0.2.3 dev_dependencies: ffi: ^2.0.2 diff --git a/packages/flutter_tools/test/general.shard/fake_native_assets_build_runner.dart b/packages/flutter_tools/test/general.shard/fake_native_assets_build_runner.dart index bcdd74c2d8b5..bd4f99042eff 100644 --- a/packages/flutter_tools/test/general.shard/fake_native_assets_build_runner.dart +++ b/packages/flutter_tools/test/general.shard/fake_native_assets_build_runner.dart @@ -49,7 +49,7 @@ class FakeNativeAssetsBuildRunner implements NativeAssetsBuildRunner { Future dryRun({ required bool includeParentEnvironment, required LinkModePreference linkModePreference, - required OS targetOs, + required OS targetOS, required Uri workingDirectory, }) async { dryRunInvocations++; diff --git a/packages/flutter_tools/test/general.shard/ios/native_assets_test.dart b/packages/flutter_tools/test/general.shard/ios/native_assets_test.dart index 0a6b7805785b..baac43c2aa35 100644 --- a/packages/flutter_tools/test/general.shard/ios/native_assets_test.dart +++ b/packages/flutter_tools/test/general.shard/ios/native_assets_test.dart @@ -142,6 +142,13 @@ void main() { ), ), ); + expect( + (globals.logger as BufferLogger).traceText, + stringContainsInOrder([ + 'Dry running native assets for ios.', + 'Dry running native assets for ios done.', + ]), + ); expect( nativeAssetsYaml, projectUri.resolve('build/native_assets/ios/native_assets.yaml'), @@ -259,13 +266,20 @@ void main() { Asset( id: 'package:bar/bar.dart', linkMode: LinkMode.dynamic, - target: native_assets_cli.Target.macOSArm64, + target: native_assets_cli.Target.iOSArm64, path: AssetAbsolutePath(Uri.file('bar.dylib')), ), ], ), ), ); + expect( + (globals.logger as BufferLogger).traceText, + stringContainsInOrder([ + 'Building native assets for [ios_arm64] debug.', + 'Building native assets for [ios_arm64] done.', + ]), + ); expect( environment.buildDir.childFile('native_assets.yaml'), exists, diff --git a/packages/flutter_tools/test/general.shard/linux/native_assets_test.dart b/packages/flutter_tools/test/general.shard/linux/native_assets_test.dart index 89797079fd27..19b5ae7016fd 100644 --- a/packages/flutter_tools/test/general.shard/linux/native_assets_test.dart +++ b/packages/flutter_tools/test/general.shard/linux/native_assets_test.dart @@ -180,6 +180,13 @@ void main() { ), ), ); + expect( + (globals.logger as BufferLogger).traceText, + stringContainsInOrder([ + 'Dry running native assets for linux.', + 'Dry running native assets for linux done.', + ]), + ); expect( nativeAssetsYaml, projectUri.resolve('build/native_assets/linux/native_assets.yaml'), @@ -222,6 +229,7 @@ void main() { await packageConfig.parent.create(); await packageConfig.create(); final (Uri? nativeAssetsYaml, _) = await buildNativeAssetsLinux( + targetPlatform: TargetPlatform.linux_x64, projectUri: projectUri, buildMode: BuildMode.debug, fileSystem: fileSystem, @@ -257,16 +265,14 @@ void main() { FeatureFlags: () => TestFeatureFlags(isNativeAssetsEnabled: true), ProcessManager: () => FakeProcessManager.empty(), }, () async { - if (const LocalPlatform().isWindows) { - return; // Backslashes in commands, but we will never run these commands on Windows. - } - final File packageConfig = environment.projectDir.childFile('.dart_tool/package_config.json'); + final File packageConfig = environment.projectDir.childDirectory('.dart_tool').childFile('package_config.json'); await packageConfig.parent.create(); await packageConfig.create(); final File dylibAfterCompiling = fileSystem.file('libbar.so'); // The mock doesn't create the file, so create it here. await dylibAfterCompiling.create(); final (Uri? nativeAssetsYaml, _) = await buildNativeAssetsLinux( + targetPlatform: TargetPlatform.linux_x64, projectUri: projectUri, buildMode: BuildMode.debug, fileSystem: fileSystem, @@ -287,6 +293,13 @@ void main() { ), ), ); + expect( + (globals.logger as BufferLogger).traceText, + stringContainsInOrder([ + 'Building native assets for linux_x64 debug.', + 'Building native assets for linux_x64 done.', + ]), + ); expect( nativeAssetsYaml, projectUri.resolve('build/native_assets/linux/native_assets.yaml'), @@ -297,7 +310,7 @@ void main() { 'package:bar/bar.dart', if (flutterTester) // Tests run on host system, so the have the full path on the system. - '- ${projectUri.resolve('/build/native_assets/linux/libbar.so').toFilePath()}' + '- ${projectUri.resolve('build/native_assets/linux/libbar.so').toFilePath()}' else // Apps are a bundle with the dylibs on their dlopen path. '- libbar.so', @@ -365,7 +378,6 @@ void main() { FileSystem: () => fileSystem, }, () async { if (!const LocalPlatform().isLinux) { - // TODO(dacoharkes): Implement other OSes. https://github.com/flutter/flutter/issues/129757 return; } diff --git a/packages/flutter_tools/test/general.shard/macos/native_assets_test.dart b/packages/flutter_tools/test/general.shard/macos/native_assets_test.dart index 07cdd2bbd374..877521bc1077 100644 --- a/packages/flutter_tools/test/general.shard/macos/native_assets_test.dart +++ b/packages/flutter_tools/test/general.shard/macos/native_assets_test.dart @@ -161,6 +161,13 @@ void main() { ), ), ); + expect( + (globals.logger as BufferLogger).traceText, + stringContainsInOrder([ + 'Dry running native assets for macos.', + 'Dry running native assets for macos done.', + ]), + ); expect( nativeAssetsYaml, projectUri.resolve('build/native_assets/macos/native_assets.yaml'), @@ -291,6 +298,13 @@ void main() { ), ), ); + expect( + (globals.logger as BufferLogger).traceText, + stringContainsInOrder([ + 'Building native assets for [macos_arm64] debug.', + 'Building native assets for [macos_arm64] done.', + ]), + ); expect( nativeAssetsYaml, projectUri.resolve('build/native_assets/macos/native_assets.yaml'), @@ -301,7 +315,7 @@ void main() { 'package:bar/bar.dart', if (flutterTester) // Tests run on host system, so the have the full path on the system. - '- ${projectUri.resolve('/build/native_assets/macos/bar.dylib').toFilePath()}' + '- ${projectUri.resolve('build/native_assets/macos/bar.dylib').toFilePath()}' else // Apps are a bundle with the dylibs on their dlopen path. '- bar.dylib', @@ -370,7 +384,6 @@ InstalledDir: /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault ), }, () async { if (!const LocalPlatform().isMacOS) { - // TODO(dacoharkes): Implement other OSes. https://github.com/flutter/flutter/issues/129757 return; } diff --git a/packages/flutter_tools/test/general.shard/migrations/cmake_project_migration_test.dart b/packages/flutter_tools/test/general.shard/migrations/cmake_project_migration_test.dart index 8689febb15ae..6ebbfa9bc270 100644 --- a/packages/flutter_tools/test/general.shard/migrations/cmake_project_migration_test.dart +++ b/packages/flutter_tools/test/general.shard/migrations/cmake_project_migration_test.dart @@ -230,9 +230,9 @@ install(DIRECTORY "${NATIVE_ASSETS_DIR}" expect(testLogger.statusText, isEmpty); }); - // TODO(dacoharkes): Add test for Windows when adding Windows support. https://github.com/flutter/flutter/issues/129757 - testWithoutContext('is migrated to copy native assets', () { - managedCmakeFile.writeAsStringSync(r''' + for (final String os in ['linux', 'windows']) { + testWithoutContext('is migrated to copy native assets', () { + managedCmakeFile.writeAsStringSync(r''' foreach(bundled_library ${PLUGIN_BUNDLED_LIBRARIES}) install(FILES "${bundled_library}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" @@ -249,38 +249,40 @@ install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) '''); - final CmakeNativeAssetsMigration cmakeProjectMigration = CmakeNativeAssetsMigration( - mockCmakeProject, - 'linux', - testLogger, - ); - cmakeProjectMigration.migrate(); - - expect(managedCmakeFile.readAsStringSync(), r''' -foreach(bundled_library ${PLUGIN_BUNDLED_LIBRARIES}) - install(FILES "${bundled_library}" - DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + final CmakeNativeAssetsMigration cmakeProjectMigration = CmakeNativeAssetsMigration( + mockCmakeProject, + os, + testLogger, + ); + cmakeProjectMigration.migrate(); + + expect(managedCmakeFile.readAsStringSync(), ''' +foreach(bundled_library \${PLUGIN_BUNDLED_LIBRARIES}) + install(FILES "\${bundled_library}" + DESTINATION "\${INSTALL_BUNDLE_LIB_DIR}" COMPONENT Runtime) endforeach(bundled_library) # Copy the native assets provided by the build.dart from all packages. -set(NATIVE_ASSETS_DIR "${PROJECT_BUILD_DIR}native_assets/linux/") -install(DIRECTORY "${NATIVE_ASSETS_DIR}" - DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" - COMPONENT Runtime) +set(NATIVE_ASSETS_DIR "\${PROJECT_BUILD_DIR}native_assets/$os/") +install(DIRECTORY "\${NATIVE_ASSETS_DIR}" + DESTINATION "\${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) # Fully re-copy the assets directory on each build to avoid having stale files # from a previous install. set(FLUTTER_ASSET_DIR_NAME "flutter_assets") install(CODE " - file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\") + file(REMOVE_RECURSE \\"\${INSTALL_BUNDLE_DATA_DIR}/\${FLUTTER_ASSET_DIR_NAME}\\") " COMPONENT Runtime) -install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}" - DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) +install(DIRECTORY "\${PROJECT_BUILD_DIR}/\${FLUTTER_ASSET_DIR_NAME}" + DESTINATION "\${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) '''); - expect(testLogger.statusText, contains('CMake missing install() NATIVE_ASSETS_DIR command, updating.')); - }); + expect(testLogger.statusText, + contains('CMake missing install() NATIVE_ASSETS_DIR command, updating.')); + }); + } }); }); } diff --git a/packages/flutter_tools/test/general.shard/windows/native_assets_test.dart b/packages/flutter_tools/test/general.shard/windows/native_assets_test.dart new file mode 100644 index 000000000000..71a52fed2355 --- /dev/null +++ b/packages/flutter_tools/test/general.shard/windows/native_assets_test.dart @@ -0,0 +1,439 @@ +// Copyright 2014 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:file/file.dart'; +import 'package:file/memory.dart'; +import 'package:file_testing/file_testing.dart'; +import 'package:flutter_tools/src/artifacts.dart'; +import 'package:flutter_tools/src/base/file_system.dart'; +import 'package:flutter_tools/src/base/logger.dart'; +import 'package:flutter_tools/src/base/platform.dart'; +import 'package:flutter_tools/src/build_info.dart'; +import 'package:flutter_tools/src/build_system/build_system.dart'; +import 'package:flutter_tools/src/dart/package_map.dart'; +import 'package:flutter_tools/src/features.dart'; +import 'package:flutter_tools/src/globals.dart' as globals; +import 'package:flutter_tools/src/native_assets.dart'; +import 'package:flutter_tools/src/windows/native_assets.dart'; +import 'package:native_assets_cli/native_assets_cli.dart' hide BuildMode, Target; +import 'package:native_assets_cli/native_assets_cli.dart' as native_assets_cli; +import 'package:package_config/package_config_types.dart'; + +import '../../src/common.dart'; +import '../../src/context.dart'; +import '../../src/fakes.dart'; +import '../fake_native_assets_build_runner.dart'; + +void main() { + late FakeProcessManager processManager; + late Environment environment; + late Artifacts artifacts; + late FileSystem fileSystem; + late BufferLogger logger; + late Uri projectUri; + + setUp(() { + processManager = FakeProcessManager.empty(); + logger = BufferLogger.test(); + artifacts = Artifacts.test(); + fileSystem = MemoryFileSystem.test(style: FileSystemStyle.windows); + environment = Environment.test( + fileSystem.currentDirectory, + inputs: {}, + artifacts: artifacts, + processManager: processManager, + fileSystem: fileSystem, + logger: logger, + ); + environment.buildDir.createSync(recursive: true); + projectUri = environment.projectDir.uri; + }); + + testUsingContext('dry run with no package config', overrides: { + ProcessManager: () => FakeProcessManager.empty(), + }, () async { + expect( + await dryRunNativeAssetsWindows( + projectUri: projectUri, + fileSystem: fileSystem, + buildRunner: FakeNativeAssetsBuildRunner( + hasPackageConfigResult: false, + ), + ), + null, + ); + expect( + (globals.logger as BufferLogger).traceText, + contains('No package config found. Skipping native assets compilation.'), + ); + }); + + testUsingContext('build with no package config', overrides: { + ProcessManager: () => FakeProcessManager.empty(), + }, () async { + await buildNativeAssetsWindows( + projectUri: projectUri, + buildMode: BuildMode.debug, + fileSystem: fileSystem, + buildRunner: FakeNativeAssetsBuildRunner( + hasPackageConfigResult: false, + ), + ); + expect( + (globals.logger as BufferLogger).traceText, + contains('No package config found. Skipping native assets compilation.'), + ); + }); + + testUsingContext('dry run for multiple OSes with no package config', overrides: { + ProcessManager: () => FakeProcessManager.empty(), + }, () async { + await dryRunNativeAssetsMultipeOSes( + projectUri: projectUri, + fileSystem: fileSystem, + targetPlatforms: [ + TargetPlatform.windows_x64, + ], + buildRunner: FakeNativeAssetsBuildRunner( + hasPackageConfigResult: false, + ), + ); + expect( + (globals.logger as BufferLogger).traceText, + contains('No package config found. Skipping native assets compilation.'), + ); + }); + + testUsingContext('dry run with assets but not enabled', overrides: { + ProcessManager: () => FakeProcessManager.empty(), + }, () async { + final File packageConfig = environment.projectDir.childFile('.dart_tool/package_config.json'); + await packageConfig.parent.create(); + await packageConfig.create(); + expect( + () => dryRunNativeAssetsWindows( + projectUri: projectUri, + fileSystem: fileSystem, + buildRunner: FakeNativeAssetsBuildRunner( + packagesWithNativeAssetsResult: [ + Package('bar', projectUri), + ], + ), + ), + throwsToolExit( + message: 'Package(s) bar require the native assets feature to be enabled. ' + 'Enable using `flutter config --enable-native-assets`.', + ), + ); + }); + + testUsingContext('dry run with assets', overrides: { + FeatureFlags: () => TestFeatureFlags(isNativeAssetsEnabled: true), + ProcessManager: () => FakeProcessManager.empty(), + }, () async { + final File packageConfig = environment.projectDir.childFile('.dart_tool/package_config.json'); + await packageConfig.parent.create(); + await packageConfig.create(); + final Uri? nativeAssetsYaml = await dryRunNativeAssetsWindows( + projectUri: projectUri, + fileSystem: fileSystem, + buildRunner: FakeNativeAssetsBuildRunner( + packagesWithNativeAssetsResult: [ + Package('bar', projectUri), + ], + dryRunResult: FakeNativeAssetsBuilderResult( + assets: [ + Asset( + id: 'package:bar/bar.dart', + linkMode: LinkMode.dynamic, + target: native_assets_cli.Target.windowsX64, + path: AssetAbsolutePath(Uri.file('bar.dll')), + ), + ], + ), + ), + ); + expect( + (globals.logger as BufferLogger).traceText, + stringContainsInOrder([ + 'Dry running native assets for windows.', + 'Dry running native assets for windows done.', + ]), + ); + expect( + nativeAssetsYaml, + projectUri.resolve('build/native_assets/windows/native_assets.yaml'), + ); + expect( + await fileSystem.file(nativeAssetsYaml).readAsString(), + contains('package:bar/bar.dart'), + ); + }); + + testUsingContext('build with assets but not enabled', overrides: { + ProcessManager: () => FakeProcessManager.empty(), + }, () async { + final File packageConfig = environment.projectDir.childFile('.dart_tool/package_config.json'); + await packageConfig.parent.create(); + await packageConfig.create(); + expect( + () => buildNativeAssetsWindows( + projectUri: projectUri, + buildMode: BuildMode.debug, + fileSystem: fileSystem, + buildRunner: FakeNativeAssetsBuildRunner( + packagesWithNativeAssetsResult: [ + Package('bar', projectUri), + ], + ), + ), + throwsToolExit( + message: 'Package(s) bar require the native assets feature to be enabled. ' + 'Enable using `flutter config --enable-native-assets`.', + ), + ); + }); + + testUsingContext('build no assets', overrides: { + FeatureFlags: () => TestFeatureFlags(isNativeAssetsEnabled: true), + ProcessManager: () => FakeProcessManager.empty(), + }, () async { + final File packageConfig = environment.projectDir.childFile('.dart_tool/package_config.json'); + await packageConfig.parent.create(); + await packageConfig.create(); + final (Uri? nativeAssetsYaml, _) = await buildNativeAssetsWindows( + targetPlatform: TargetPlatform.windows_x64, + projectUri: projectUri, + buildMode: BuildMode.debug, + fileSystem: fileSystem, + buildRunner: FakeNativeAssetsBuildRunner( + packagesWithNativeAssetsResult: [ + Package('bar', projectUri), + ], + ), + ); + expect( + nativeAssetsYaml, + projectUri.resolve('build/native_assets/windows/native_assets.yaml'), + ); + expect( + await fileSystem.file(nativeAssetsYaml).readAsString(), + isNot(contains('package:bar/bar.dart')), + ); + expect( + environment.projectDir.childDirectory('build').childDirectory('native_assets').childDirectory('windows'), + exists, + ); + }); + + for (final bool flutterTester in [false, true]) { + String testName = ''; + if (flutterTester) { + testName += ' flutter tester'; + } + testUsingContext('build with assets$testName', overrides: { + FeatureFlags: () => TestFeatureFlags(isNativeAssetsEnabled: true), + ProcessManager: () => FakeProcessManager.empty(), + }, () async { + final File packageConfig = environment.projectDir.childDirectory('.dart_tool').childFile('package_config.json'); + await packageConfig.parent.create(); + await packageConfig.create(); + final File dylibAfterCompiling = fileSystem.file('bar.dll'); + // The mock doesn't create the file, so create it here. + await dylibAfterCompiling.create(); + final (Uri? nativeAssetsYaml, _) = await buildNativeAssetsWindows( + targetPlatform: TargetPlatform.windows_x64, + projectUri: projectUri, + buildMode: BuildMode.debug, + fileSystem: fileSystem, + flutterTester: flutterTester, + buildRunner: FakeNativeAssetsBuildRunner( + packagesWithNativeAssetsResult: [ + Package('bar', projectUri), + ], + buildResult: FakeNativeAssetsBuilderResult( + assets: [ + Asset( + id: 'package:bar/bar.dart', + linkMode: LinkMode.dynamic, + target: native_assets_cli.Target.windowsX64, + path: AssetAbsolutePath(dylibAfterCompiling.uri), + ), + ], + ), + ), + ); + expect( + (globals.logger as BufferLogger).traceText, + stringContainsInOrder([ + 'Building native assets for windows_x64 debug.', + 'Building native assets for windows_x64 done.', + ]), + ); + expect( + nativeAssetsYaml, + projectUri.resolve('build/native_assets/windows/native_assets.yaml'), + ); + expect( + await fileSystem.file(nativeAssetsYaml).readAsString(), + stringContainsInOrder([ + 'package:bar/bar.dart', + if (flutterTester) + // Tests run on host system, so the have the full path on the system. + '- ${projectUri.resolve('build/native_assets/windows/bar.dll').toFilePath()}' + else + // Apps are a bundle with the dylibs on their dlopen path. + '- bar.dll', + ]), + ); + }); + } + + testUsingContext('static libs not supported', overrides: { + FeatureFlags: () => TestFeatureFlags(isNativeAssetsEnabled: true), + ProcessManager: () => FakeProcessManager.empty(), + }, () async { + final File packageConfig = environment.projectDir.childFile('.dart_tool/package_config.json'); + await packageConfig.parent.create(); + await packageConfig.create(); + expect( + () => dryRunNativeAssetsWindows( + projectUri: projectUri, + fileSystem: fileSystem, + buildRunner: FakeNativeAssetsBuildRunner( + packagesWithNativeAssetsResult: [ + Package('bar', projectUri), + ], + dryRunResult: FakeNativeAssetsBuilderResult( + assets: [ + Asset( + id: 'package:bar/bar.dart', + linkMode: LinkMode.static, + target: native_assets_cli.Target.windowsX64, + path: AssetAbsolutePath(Uri.file(OS.windows.staticlibFileName('bar'))), + ), + ], + ), + ), + ), + throwsToolExit( + message: 'Native asset(s) package:bar/bar.dart have their link mode set to ' + 'static, but this is not yet supported. ' + 'For more info see https://github.com/dart-lang/sdk/issues/49418.', + ), + ); + }); + + // This logic is mocked in the other tests to avoid having test order + // randomization causing issues with what processes are invoked. + // Exercise the parsing of the process output in this separate test. + testUsingContext('NativeAssetsBuildRunnerImpl.cCompilerConfig', overrides: { + FeatureFlags: () => TestFeatureFlags(isNativeAssetsEnabled: true), + ProcessManager: () => FakeProcessManager.list( + [ + FakeCommand( + command: [ + RegExp(r'(.*)vswhere.exe'), + '-format', + 'json', + '-products', + '*', + '-utf8', + '-latest', + '-version', + '16', + '-requires', + 'Microsoft.VisualStudio.Workload.NativeDesktop', + 'Microsoft.VisualStudio.Component.VC.Tools.x86.x64', + 'Microsoft.VisualStudio.Component.VC.CMake.Project', + ], + stdout: r''' +[ + { + "instanceId": "491ec752", + "installDate": "2023-04-21T08:17:11Z", + "installationName": "VisualStudio/17.5.4+33530.505", + "installationPath": "C:\\Program Files\\Microsoft Visual Studio\\2022\\Community", + "installationVersion": "17.5.33530.505", + "productId": "Microsoft.VisualStudio.Product.Community", + "productPath": "C:\\Program Files\\Microsoft Visual Studio\\2022\\Community\\Common7\\IDE\\devenv.exe", + "state": 4294967295, + "isComplete": true, + "isLaunchable": true, + "isPrerelease": false, + "isRebootRequired": false, + "displayName": "Visual Studio Community 2022", + "description": "Powerful IDE, free for students, open-source contributors, and individuals", + "channelId": "VisualStudio.17.Release", + "channelUri": "https://aka.ms/vs/17/release/channel", + "enginePath": "C:\\Program Files (x86)\\Microsoft Visual Studio\\Installer\\resources\\app\\ServiceHub\\Services\\Microsoft.VisualStudio.Setup.Service", + "installedChannelId": "VisualStudio.17.Release", + "installedChannelUri": "https://aka.ms/vs/17/release/channel", + "releaseNotes": "https://docs.microsoft.com/en-us/visualstudio/releases/2022/release-notes-v17.5#17.5.4", + "thirdPartyNotices": "https://go.microsoft.com/fwlink/?LinkId=661288", + "updateDate": "2023-04-21T08:17:11.2249473Z", + "catalog": { + "buildBranch": "d17.5", + "buildVersion": "17.5.33530.505", + "id": "VisualStudio/17.5.4+33530.505", + "localBuild": "build-lab", + "manifestName": "VisualStudio", + "manifestType": "installer", + "productDisplayVersion": "17.5.4", + "productLine": "Dev17", + "productLineVersion": "2022", + "productMilestone": "RTW", + "productMilestoneIsPreRelease": "False", + "productName": "Visual Studio", + "productPatchVersion": "4", + "productPreReleaseMilestoneSuffix": "1.0", + "productSemanticVersion": "17.5.4+33530.505", + "requiredEngineVersion": "3.5.2150.18781" + }, + "properties": { + "campaignId": "2060:abb99c5d1ecc4013acf2e1814b10b690", + "channelManifestId": "VisualStudio.17.Release/17.5.4+33530.505", + "nickname": "", + "setupEngineFilePath": "C:\\Program Files (x86)\\Microsoft Visual Studio\\Installer\\setup.exe" + } + } +] +''', // Newline at the end of the string. + ) + ], + ), + FileSystem: () => fileSystem, + }, () async { + if (!const LocalPlatform().isWindows) { + return; + } + + final Directory msvcBinDir = + fileSystem.directory(r'C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Tools\MSVC\14.35.32215\bin\Hostx64\x64'); + await msvcBinDir.create(recursive: true); + + final File packagesFile = fileSystem + .directory(projectUri) + .childDirectory('.dart_tool') + .childFile('package_config.json'); + await packagesFile.parent.create(); + await packagesFile.create(); + final PackageConfig packageConfig = await loadPackageConfigWithLogging( + packagesFile, + logger: environment.logger, + ); + final NativeAssetsBuildRunner runner = NativeAssetsBuildRunnerImpl( + projectUri, + packageConfig, + fileSystem, + logger, + ); + final CCompilerConfig result = await runner.cCompilerConfig; + expect(result.cc?.toFilePath(), msvcBinDir.childFile('cl.exe').uri.toFilePath()); + expect(result.ar?.toFilePath(), msvcBinDir.childFile('lib.exe').uri.toFilePath()); + expect(result.ld?.toFilePath(), msvcBinDir.childFile('link.exe').uri.toFilePath()); + expect(result.envScript, isNotNull); + expect(result.envScriptArgs, isNotNull); + }); +} diff --git a/packages/flutter_tools/test/general.shard/windows/visual_studio_test.dart b/packages/flutter_tools/test/general.shard/windows/visual_studio_test.dart index 6f2aa45411a3..57b07e929e28 100644 --- a/packages/flutter_tools/test/general.shard/windows/visual_studio_test.dart +++ b/packages/flutter_tools/test/general.shard/windows/visual_studio_test.dart @@ -11,12 +11,16 @@ import 'package:flutter_tools/src/convert.dart'; import 'package:flutter_tools/src/windows/visual_studio.dart'; import '../../src/common.dart'; -import '../../src/fake_process_manager.dart'; +import '../../src/context.dart'; const String programFilesPath = r'C:\Program Files (x86)'; const String visualStudioPath = programFilesPath + r'\Microsoft Visual Studio\2017\Community'; const String cmakePath = visualStudioPath + r'\Common7\IDE\CommonExtensions\Microsoft\CMake\CMake\bin\cmake.exe'; const String vswherePath = programFilesPath + r'\Microsoft Visual Studio\Installer\vswhere.exe'; +const String clPath = visualStudioPath + r'\VC\Tools\MSVC\14.35.32215\bin\Hostx64\x64\cl.exe'; +const String libPath = visualStudioPath + r'\VC\Tools\MSVC\14.35.32215\bin\Hostx64\x64\lib.exe'; +const String linkPath = visualStudioPath + r'\VC\Tools\MSVC\14.35.32215\bin\Hostx64\x64\link.exe'; +const String vcvarsPath = visualStudioPath + r'\VC\Auxiliary\Build\vcvars64.bat'; final Platform windowsPlatform = FakePlatform( operatingSystem: 'windows', @@ -140,6 +144,7 @@ void setMockVswhereResponse( ]) { fileSystem.file(vswherePath).createSync(recursive: true); fileSystem.file(cmakePath).createSync(recursive: true); + fileSystem.file(clPath).createSync(recursive: true); final String finalResponse = responseOverride ?? (response != null ? json.encode(>[response]) : '[]'); final List requirementArguments = requiredComponents == null @@ -300,11 +305,11 @@ void setMockSdkRegResponse( const String registryKey = r'InstallationFolder'; const String installationPath = r'C:\Program Files (x86)\Windows Kits\10\'; final String stdout = registryPresent - ? ''' + ? ''' $registryPath $registryKey REG_SZ $installationPath ''' - : ''' + : ''' ERROR: The system was unable to find the specified registry key or value. '''; @@ -776,6 +781,10 @@ void main() { expect(visualStudio.hasNecessaryComponents, true); expect(visualStudio.cmakePath, equals(cmakePath)); expect(visualStudio.cmakeGenerator, equals('Visual Studio 16 2019')); + expect(visualStudio.clPath, equals(clPath)); + expect(visualStudio.libPath, equals(libPath)); + expect(visualStudio.linkPath, equals(linkPath)); + expect(visualStudio.vcvarsPath, equals(vcvarsPath)); }); testWithoutContext('Everything returns good values when Build Tools is present with all components', () { @@ -814,6 +823,10 @@ void main() { expect(visualStudio.hasNecessaryComponents, true); expect(visualStudio.cmakePath, equals(cmakePath)); expect(visualStudio.cmakeGenerator, equals('Visual Studio 17 2022')); + expect(visualStudio.clPath, equals(clPath)); + expect(visualStudio.libPath, equals(libPath)); + expect(visualStudio.linkPath, equals(linkPath)); + expect(visualStudio.vcvarsPath, equals(vcvarsPath)); }); testWithoutContext('Metadata is for compatible version when latest is missing components', () { @@ -905,8 +918,7 @@ void main() { }); testWithoutContext('Ignores unicode replacement char in unused properties', () { - final Map response = Map.of(_defaultResponse) - ..['unused'] = 'Bad UTF8 \u{FFFD}'; + final Map response = Map.of(_defaultResponse)..['unused'] = 'Bad UTF8 \u{FFFD}'; setMockCompatibleVisualStudioInstallation( response, @@ -919,6 +931,10 @@ void main() { expect(visualStudio.hasNecessaryComponents, true); expect(visualStudio.cmakePath, equals(cmakePath)); expect(visualStudio.cmakeGenerator, equals('Visual Studio 16 2019')); + expect(visualStudio.clPath, equals(clPath)); + expect(visualStudio.libPath, equals(libPath)); + expect(visualStudio.linkPath, equals(linkPath)); + expect(visualStudio.vcvarsPath, equals(vcvarsPath)); }); testWithoutContext('Throws ToolExit on bad UTF-8 in installationPath', () { @@ -953,13 +969,16 @@ void main() { expect(visualStudio.cmakePath, equals(cmakePath)); expect(visualStudio.cmakeGenerator, equals('Visual Studio 16 2019')); expect(visualStudio.displayName, equals('\u{FFFD}')); + expect(visualStudio.clPath, equals(clPath)); + expect(visualStudio.libPath, equals(libPath)); + expect(visualStudio.linkPath, equals(linkPath)); + expect(visualStudio.vcvarsPath, equals(vcvarsPath)); }); testWithoutContext("Ignores bad UTF-8 in catalog's productDisplayVersion", () { final Map catalog = Map.of(_defaultResponse['catalog'] as Map) ..['productDisplayVersion'] = '\u{FFFD}'; - final Map response = Map.of(_defaultResponse) - ..['catalog'] = catalog; + final Map response = Map.of(_defaultResponse)..['catalog'] = catalog; setMockCompatibleVisualStudioInstallation(response, fixture.fileSystem, fixture.processManager); @@ -969,6 +988,10 @@ void main() { expect(visualStudio.cmakePath, equals(cmakePath)); expect(visualStudio.cmakeGenerator, equals('Visual Studio 16 2019')); expect(visualStudio.displayVersion, equals('\u{FFFD}')); + expect(visualStudio.clPath, equals(clPath)); + expect(visualStudio.libPath, equals(libPath)); + expect(visualStudio.linkPath, equals(linkPath)); + expect(visualStudio.vcvarsPath, equals(vcvarsPath)); }); testWithoutContext('Ignores malformed JSON in description property', () { @@ -987,101 +1010,109 @@ void main() { expect(visualStudio.cmakePath, equals(cmakePath)); expect(visualStudio.cmakeGenerator, equals('Visual Studio 16 2019')); expect(visualStudio.displayVersion, equals('16.2.5')); + expect(visualStudio.clPath, equals(clPath)); + expect(visualStudio.libPath, equals(libPath)); + expect(visualStudio.linkPath, equals(linkPath)); + expect(visualStudio.vcvarsPath, equals(vcvarsPath)); expect(fixture.logger.warningText, isEmpty); }); }); group(VswhereDetails, () { - test('Accepts empty JSON', () { - const bool meetsRequirements = true; - final Map json = {}; - - final VswhereDetails result = VswhereDetails.fromJson(meetsRequirements, json); - - expect(result.installationPath, null); - expect(result.displayName, null); - expect(result.fullVersion, null); - expect(result.isComplete, null); - expect(result.isLaunchable, null); - expect(result.isRebootRequired, null); - expect(result.isPrerelease, null); - expect(result.catalogDisplayVersion, null); - expect(result.isUsable, isTrue); - }); - - test('Ignores unknown JSON properties', () { - const bool meetsRequirements = true; - final Map json = { - 'hello': 'world', - }; - - final VswhereDetails result = VswhereDetails.fromJson(meetsRequirements, json); - - expect(result.installationPath, null); - expect(result.displayName, null); - expect(result.fullVersion, null); - expect(result.isComplete, null); - expect(result.isLaunchable, null); - expect(result.isRebootRequired, null); - expect(result.isPrerelease, null); - expect(result.catalogDisplayVersion, null); - expect(result.isUsable, isTrue); - }); - - test('Accepts JSON', () { - const bool meetsRequirements = true; - - final VswhereDetails result = VswhereDetails.fromJson(meetsRequirements, _defaultResponse); - - expect(result.installationPath, visualStudioPath); - expect(result.displayName, 'Visual Studio Community 2019'); - expect(result.fullVersion, '16.2.29306.81'); - expect(result.isComplete, true); - expect(result.isLaunchable, true); - expect(result.isRebootRequired, false); - expect(result.isPrerelease, false); - expect(result.catalogDisplayVersion, '16.2.5'); - expect(result.isUsable, isTrue); - }); - - test('Installation that does not satisfy requirements is not usable', () { - const bool meetsRequirements = false; - - final VswhereDetails result = VswhereDetails.fromJson(meetsRequirements, _defaultResponse); - - expect(result.isUsable, isFalse); - }); - - test('Incomplete installation is not usable', () { - const bool meetsRequirements = true; - final Map json = Map.of(_defaultResponse) - ..['isComplete'] = false; - - final VswhereDetails result = VswhereDetails.fromJson(meetsRequirements, json); - - expect(result.isUsable, isFalse); - }); - - test('Unlaunchable installation is not usable', () { - const bool meetsRequirements = true; - final Map json = Map.of(_defaultResponse) - ..['isLaunchable'] = false; - - final VswhereDetails result = VswhereDetails.fromJson(meetsRequirements, json); - - expect(result.isUsable, isFalse); - }); - - test('Installation that requires reboot is not usable', () { - const bool meetsRequirements = true; - final Map json = Map.of(_defaultResponse) - ..['isRebootRequired'] = true; - - final VswhereDetails result = VswhereDetails.fromJson(meetsRequirements, json); - - expect(result.isUsable, isFalse); - }); + test('Accepts empty JSON', () { + const bool meetsRequirements = true; + final Map json = {}; + const String msvcVersion = ''; + + final VswhereDetails result = VswhereDetails.fromJson(meetsRequirements, json, msvcVersion); + + expect(result.installationPath, null); + expect(result.displayName, null); + expect(result.fullVersion, null); + expect(result.isComplete, null); + expect(result.isLaunchable, null); + expect(result.isRebootRequired, null); + expect(result.isPrerelease, null); + expect(result.catalogDisplayVersion, null); + expect(result.isUsable, isTrue); + }); + + test('Ignores unknown JSON properties', () { + const bool meetsRequirements = true; + final Map json = { + 'hello': 'world', + }; + const String msvcVersion = ''; + + final VswhereDetails result = VswhereDetails.fromJson(meetsRequirements, json, msvcVersion); + + expect(result.installationPath, null); + expect(result.displayName, null); + expect(result.fullVersion, null); + expect(result.isComplete, null); + expect(result.isLaunchable, null); + expect(result.isRebootRequired, null); + expect(result.isPrerelease, null); + expect(result.catalogDisplayVersion, null); + expect(result.isUsable, isTrue); + }); + + test('Accepts JSON', () { + const bool meetsRequirements = true; + const String msvcVersion = ''; + + final VswhereDetails result = VswhereDetails.fromJson(meetsRequirements, _defaultResponse, msvcVersion); + + expect(result.installationPath, visualStudioPath); + expect(result.displayName, 'Visual Studio Community 2019'); + expect(result.fullVersion, '16.2.29306.81'); + expect(result.isComplete, true); + expect(result.isLaunchable, true); + expect(result.isRebootRequired, false); + expect(result.isPrerelease, false); + expect(result.catalogDisplayVersion, '16.2.5'); + expect(result.isUsable, isTrue); + }); + + test('Installation that does not satisfy requirements is not usable', () { + const bool meetsRequirements = false; + const String msvcVersion = ''; + + final VswhereDetails result = VswhereDetails.fromJson(meetsRequirements, _defaultResponse, msvcVersion); + + expect(result.isUsable, isFalse); + }); + + test('Incomplete installation is not usable', () { + const bool meetsRequirements = true; + final Map json = Map.of(_defaultResponse)..['isComplete'] = false; + const String msvcVersion = ''; + + final VswhereDetails result = VswhereDetails.fromJson(meetsRequirements, json, msvcVersion); + + expect(result.isUsable, isFalse); + }); + + test('Unlaunchable installation is not usable', () { + const bool meetsRequirements = true; + final Map json = Map.of(_defaultResponse)..['isLaunchable'] = false; + const String msvcVersion = ''; + + final VswhereDetails result = VswhereDetails.fromJson(meetsRequirements, json, msvcVersion); + + expect(result.isUsable, isFalse); + }); + + test('Installation that requires reboot is not usable', () { + const bool meetsRequirements = true; + final Map json = Map.of(_defaultResponse)..['isRebootRequired'] = true; + const String msvcVersion = ''; + + final VswhereDetails result = VswhereDetails.fromJson(meetsRequirements, json, msvcVersion); + + expect(result.isUsable, isFalse); + }); }); } diff --git a/packages/flutter_tools/test/integration.shard/native_assets_test.dart b/packages/flutter_tools/test/integration.shard/native_assets_test.dart index c6824fb35774..3456c6a224b6 100644 --- a/packages/flutter_tools/test/integration.shard/native_assets_test.dart +++ b/packages/flutter_tools/test/integration.shard/native_assets_test.dart @@ -58,8 +58,8 @@ const String packageName = 'package_with_native_assets'; const String exampleAppName = '${packageName}_example'; void main() { - if (!platform.isMacOS && !platform.isLinux) { - // TODO(dacoharkes): Implement other OSes. https://github.com/flutter/flutter/issues/129757 + if (!platform.isMacOS && !platform.isLinux && !platform.isWindows) { + // TODO(dacoharkes): Implement Fuchsia. https://github.com/flutter/flutter/issues/129757 return; } @@ -146,9 +146,10 @@ void main() { if (device == 'macos') { expectDylibIsBundledMacOS(exampleDirectory, buildMode); - } - if (device == 'linux') { + } else if (device == 'linux') { expectDylibIsBundledLinux(exampleDirectory, buildMode); + } else if (device == 'windows') { + expectDylibIsBundledWindows(exampleDirectory, buildMode); } if (device == hostOs) { expectCCompilerIsConfigured(exampleDirectory); @@ -205,6 +206,8 @@ void main() { expectDylibIsBundledIos(exampleDirectory, buildMode); } else if (buildSubcommand == 'linux') { expectDylibIsBundledLinux(exampleDirectory, buildMode); + } else if (buildSubcommand == 'windows') { + expectDylibIsBundledWindows(exampleDirectory, buildMode); } expectCCompilerIsConfigured(exampleDirectory); }); @@ -239,11 +242,15 @@ void main() { 'build', buildSubcommand, if (buildSubcommand == 'ios') '--no-codesign', + if (buildSubcommand == 'windows') '-v' // Requires verbose mode for error. ], workingDirectory: exampleDirectory.path, ); expect(result.exitCode, isNot(0)); - expect(result.stderr, contains('link mode set to static, but this is not yet supported')); + expect( + (result.stdout as String) + (result.stderr as String), + contains('link mode set to static, but this is not yet supported'), + ); }); }); } @@ -301,14 +308,36 @@ void expectDylibIsBundledIos(Directory appDirectory, String buildMode) { void expectDylibIsBundledLinux(Directory appDirectory, String buildMode) { // Linux does not support cross compilation, so always only check current architecture. final String architecture = Architecture.current.dartPlatform; - final Directory appBundle = appDirectory.childDirectory('build/$hostOs/$architecture/$buildMode/bundle/'); + final Directory appBundle = appDirectory + .childDirectory('build') + .childDirectory(hostOs) + .childDirectory(architecture) + .childDirectory(buildMode) + .childDirectory('bundle'); expect(appBundle, exists); - final Directory dylibsFolder = appBundle.childDirectory('lib/'); + final Directory dylibsFolder = appBundle.childDirectory('lib'); expect(dylibsFolder, exists); final File dylib = dylibsFolder.childFile(OS.linux.dylibFileName(packageName)); expect(dylib, exists); } +/// Checks that dylibs are bundled. +/// +/// Sample path: build\windows\x64\runner\Debug\my_package_example.exe +void expectDylibIsBundledWindows(Directory appDirectory, String buildMode) { + // Linux does not support cross compilation, so always only check current architecture. + final String architecture = Architecture.current.dartPlatform; + final Directory appBundle = appDirectory + .childDirectory('build') + .childDirectory(hostOs) + .childDirectory(architecture) + .childDirectory('runner') + .childDirectory(buildMode.upperCaseFirst()); + expect(appBundle, exists); + final File dylib = appBundle.childFile(OS.windows.dylibFileName(packageName)); + expect(dylib, exists); +} + /// For `flutter build` we can't easily test whether running the app works. /// Check that we have the dylibs in the app. void expectDylibIsBundledWithFrameworks(Directory appDirectory, String buildMode, String os) {