Unverified Commit 4b120501 authored by stuartmorgan's avatar stuartmorgan Committed by GitHub

Switch Windows to CMake (#60629)

* First pass at CMake files; untested

* First pass of adding CMake generation logic on Windows

* Misc fixes

* Get bundling working, start incoprorating CMake build into tool

* Fix debug, exe name.

* Add resources

* Move cmake.dart

* Rip out all the vcxproj/solution plumbing

* Fix plugin cmake generation

* Build with cmake rather than calling VS directly

* Adjust Windows plugin template to match standard header directory structure

* Pass config selection when building

* Partially fix multi-config handling

* Rev template version

* Share the CMake generation instead of splitting it out

* VS build/run cycle works, with slightly awkward requirement to always build all

* Update manifest

* Plugin template fixes

* Minor adjustments

* Build install as part of build command, instead of separately

* Test cleanup

* Update Linux test for adjusted generated CMake approach

* Plugin test typo fix

* Add missing stub file for project test

* Add a constant for VS generator
parent 35df9781
@ECHO off
REM Copyright 2014 The Flutter Authors. All rights reserved.
REM Use of this source code is governed by a BSD-style license that can be
REM found in the LICENSE file.
REM Calls vcvars64.bat to configure a command-line build environment, then builds
REM a project with msbuild.
@echo off
set VCVARS=%~1
set PROJECT=%~2
set CONFIG=%~3
call "%VCVARS%"
if %errorlevel% neq 0 exit /b %errorlevel%
msbuild "%PROJECT%" /p:Configuration=%CONFIG%
...@@ -2,12 +2,12 @@ ...@@ -2,12 +2,12 @@
// 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.
import '../project.dart'; import 'project.dart';
/// Extracts the `BINARY_NAME` from a Linux project CMake file. /// Extracts the `BINARY_NAME` from a project's CMake file.
/// ///
/// Returns `null` if it cannot be found. /// Returns `null` if it cannot be found.
String getCmakeExecutableName(LinuxProject project) { String getCmakeExecutableName(CmakeBasedProject project) {
if (!project.cmakeFile.existsSync()) { if (!project.cmakeFile.existsSync()) {
return null; return null;
} }
...@@ -21,24 +21,30 @@ String getCmakeExecutableName(LinuxProject project) { ...@@ -21,24 +21,30 @@ String getCmakeExecutableName(LinuxProject project) {
return null; return null;
} }
String _escapeBackslashes(String s) {
return s.replaceAll(r'\', r'\\');
}
/// Writes a generated CMake configuration file for [project], including /// Writes a generated CMake configuration file for [project], including
/// variables expected by the build template and an environment variable list /// variables expected by the build template and an environment variable list
/// for calling back into Flutter. /// for calling back into Flutter.
void writeGeneratedCmakeConfig(String flutterRoot, LinuxProject project, Map<String, String> environment) { void writeGeneratedCmakeConfig(String flutterRoot, CmakeBasedProject project, Map<String, String> environment) {
// Only a limited set of variables are needed by the CMake files themselves, // Only a limited set of variables are needed by the CMake files themselves,
// the rest are put into a list to pass to the re-entrant build step. // the rest are put into a list to pass to the re-entrant build step.
final String escapedFlutterRoot = _escapeBackslashes(flutterRoot);
final String escapedProjectDir = _escapeBackslashes(project.parent.directory.path);
final StringBuffer buffer = StringBuffer(''' final StringBuffer buffer = StringBuffer('''
# Generated code do not commit. # Generated code do not commit.
set(FLUTTER_ROOT "$flutterRoot") file(TO_CMAKE_PATH "$escapedFlutterRoot" FLUTTER_ROOT)
set(PROJECT_DIR "${project.project.directory.path}") file(TO_CMAKE_PATH "$escapedProjectDir" PROJECT_DIR)
# Environment variables to pass to tool_backend.sh # Environment variables to pass to tool_backend.sh
list(APPEND FLUTTER_TOOL_ENVIRONMENT list(APPEND FLUTTER_TOOL_ENVIRONMENT
"FLUTTER_ROOT=\\"\${FLUTTER_ROOT}\\"" "FLUTTER_ROOT=\\"$escapedFlutterRoot\\""
"PROJECT_DIR=\\"\${PROJECT_DIR}\\"" "PROJECT_DIR=\\"$escapedProjectDir\\""
'''); ''');
for (final String key in environment.keys) { for (final String key in environment.keys) {
final String value = environment[key]; final String value = _escapeBackslashes(environment[key]);
buffer.writeln(' "$key=\\"$value\\""'); buffer.writeln(' "$key=\\"$value\\""');
} }
buffer.writeln(')'); buffer.writeln(')');
......
...@@ -7,9 +7,9 @@ import 'package:meta/meta.dart'; ...@@ -7,9 +7,9 @@ import 'package:meta/meta.dart';
import '../application_package.dart'; import '../application_package.dart';
import '../base/file_system.dart'; import '../base/file_system.dart';
import '../build_info.dart'; import '../build_info.dart';
import '../cmake.dart';
import '../globals.dart' as globals; import '../globals.dart' as globals;
import '../project.dart'; import '../project.dart';
import 'cmake.dart';
abstract class LinuxApp extends ApplicationPackage { abstract class LinuxApp extends ApplicationPackage {
LinuxApp({@required String projectBundleId}) : super(id: projectBundleId); LinuxApp({@required String projectBundleId}) : super(id: projectBundleId);
...@@ -52,7 +52,7 @@ class PrebuiltLinuxApp extends LinuxApp { ...@@ -52,7 +52,7 @@ class PrebuiltLinuxApp extends LinuxApp {
} }
class BuildableLinuxApp extends LinuxApp { class BuildableLinuxApp extends LinuxApp {
BuildableLinuxApp({this.project}) : super(projectBundleId: project.project.manifest.appName); BuildableLinuxApp({this.project}) : super(projectBundleId: project.parent.manifest.appName);
final LinuxProject project; final LinuxProject project;
...@@ -68,5 +68,5 @@ class BuildableLinuxApp extends LinuxApp { ...@@ -68,5 +68,5 @@ class BuildableLinuxApp extends LinuxApp {
} }
@override @override
String get name => project.project.manifest.appName; String get name => project.parent.manifest.appName;
} }
...@@ -10,10 +10,10 @@ import '../base/process.dart'; ...@@ -10,10 +10,10 @@ import '../base/process.dart';
import '../base/utils.dart'; import '../base/utils.dart';
import '../build_info.dart'; import '../build_info.dart';
import '../cache.dart'; import '../cache.dart';
import '../cmake.dart';
import '../globals.dart' as globals; import '../globals.dart' as globals;
import '../plugins.dart'; import '../plugins.dart';
import '../project.dart'; import '../project.dart';
import 'cmake.dart';
/// Builds the Linux project through the Makefile. /// Builds the Linux project through the Makefile.
Future<void> buildLinux( Future<void> buildLinux(
...@@ -39,7 +39,7 @@ Future<void> buildLinux( ...@@ -39,7 +39,7 @@ Future<void> buildLinux(
} }
writeGeneratedCmakeConfig(Cache.flutterRoot, linuxProject, environmentConfig); writeGeneratedCmakeConfig(Cache.flutterRoot, linuxProject, environmentConfig);
createPluginSymlinks(linuxProject.project); createPluginSymlinks(linuxProject.parent);
final Status status = globals.logger.startProgress( final Status status = globals.logger.startProgress(
'Building Linux application...', 'Building Linux application...',
......
...@@ -6,6 +6,7 @@ import 'dart:async'; ...@@ -6,6 +6,7 @@ import 'dart:async';
import 'package:meta/meta.dart'; import 'package:meta/meta.dart';
import 'package:package_config/package_config.dart'; import 'package:package_config/package_config.dart';
import 'package:path/path.dart' as path; // ignore: package_path_import
import 'package:yaml/yaml.dart'; import 'package:yaml/yaml.dart';
import 'android/gradle.dart'; import 'android/gradle.dart';
...@@ -17,8 +18,6 @@ import 'features.dart'; ...@@ -17,8 +18,6 @@ import 'features.dart';
import 'globals.dart' as globals; import 'globals.dart' as globals;
import 'platform_plugins.dart'; import 'platform_plugins.dart';
import 'project.dart'; import 'project.dart';
import 'windows/property_sheet.dart';
import 'windows/visual_studio_solution_utils.dart';
void _renderTemplateToFile(String template, dynamic context, String filePath) { void _renderTemplateToFile(String template, dynamic context, String filePath) {
final String renderedTemplate = globals.templateRenderer final String renderedTemplate = globals.templateRenderer
...@@ -801,7 +800,7 @@ const String _cppPluginRegistryImplementationTemplate = ''' ...@@ -801,7 +800,7 @@ const String _cppPluginRegistryImplementationTemplate = '''
#include "generated_plugin_registrant.h" #include "generated_plugin_registrant.h"
{{#plugins}} {{#plugins}}
#include <{{filename}}.h> #include <{{name}}/{{filename}}.h>
{{/plugins}} {{/plugins}}
void RegisterPlugins(flutter::PluginRegistry* registry) { void RegisterPlugins(flutter::PluginRegistry* registry) {
...@@ -848,7 +847,7 @@ void fl_register_plugins(FlPluginRegistry* registry) { ...@@ -848,7 +847,7 @@ void fl_register_plugins(FlPluginRegistry* registry) {
} }
'''; ''';
const String _linuxPluginCmakefileTemplate = r''' const String _pluginCmakefileTemplate = r'''
# #
# Generated file, do not edit. # Generated file, do not edit.
# #
...@@ -862,7 +861,7 @@ list(APPEND FLUTTER_PLUGIN_LIST ...@@ -862,7 +861,7 @@ list(APPEND FLUTTER_PLUGIN_LIST
set(PLUGIN_BUNDLED_LIBRARIES) set(PLUGIN_BUNDLED_LIBRARIES)
foreach(plugin ${FLUTTER_PLUGIN_LIST}) foreach(plugin ${FLUTTER_PLUGIN_LIST})
add_subdirectory({{pluginsDir}}/${plugin}/linux plugins/${plugin}) add_subdirectory({{pluginsDir}}/${plugin}/{{os}} plugins/${plugin})
target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin)
list(APPEND PLUGIN_BUNDLED_LIBRARIES $<TARGET_FILE:${plugin}_plugin>) list(APPEND PLUGIN_BUNDLED_LIBRARIES $<TARGET_FILE:${plugin}_plugin>)
list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries})
...@@ -917,6 +916,7 @@ Future<void> _writeLinuxPluginFiles(FlutterProject project, List<Plugin> plugins ...@@ -917,6 +916,7 @@ Future<void> _writeLinuxPluginFiles(FlutterProject project, List<Plugin> plugins
// that file's directory. // that file's directory.
final String makefileDirPath = project.linux.cmakeFile.parent.absolute.path; final String makefileDirPath = project.linux.cmakeFile.parent.absolute.path;
final Map<String, dynamic> context = <String, dynamic>{ final Map<String, dynamic> context = <String, dynamic>{
'os': 'linux',
'plugins': linuxPlugins, 'plugins': linuxPlugins,
'pluginsDir': globals.fs.path.relative( 'pluginsDir': globals.fs.path.relative(
project.linux.pluginSymlinkDirectory.absolute.path, project.linux.pluginSymlinkDirectory.absolute.path,
...@@ -924,7 +924,7 @@ Future<void> _writeLinuxPluginFiles(FlutterProject project, List<Plugin> plugins ...@@ -924,7 +924,7 @@ Future<void> _writeLinuxPluginFiles(FlutterProject project, List<Plugin> plugins
), ),
}; };
await _writeLinuxPluginRegistrant(project.linux.managedDirectory, context); await _writeLinuxPluginRegistrant(project.linux.managedDirectory, context);
await _writeLinuxPluginCmakefile(project.linux.generatedPluginCmakeFile, context); await _writePluginCmakefile(project.linux.generatedPluginCmakeFile, context);
} }
Future<void> _writeLinuxPluginRegistrant(Directory destination, Map<String, dynamic> templateContext) async { Future<void> _writeLinuxPluginRegistrant(Directory destination, Map<String, dynamic> templateContext) async {
...@@ -941,9 +941,9 @@ Future<void> _writeLinuxPluginRegistrant(Directory destination, Map<String, dyna ...@@ -941,9 +941,9 @@ Future<void> _writeLinuxPluginRegistrant(Directory destination, Map<String, dyna
); );
} }
Future<void> _writeLinuxPluginCmakefile(File destinationFile, Map<String, dynamic> templateContext) async { Future<void> _writePluginCmakefile(File destinationFile, Map<String, dynamic> templateContext) async {
_renderTemplateToFile( _renderTemplateToFile(
_linuxPluginCmakefileTemplate, _pluginCmakefileTemplate,
templateContext, templateContext,
destinationFile.path, destinationFile.path,
); );
...@@ -984,11 +984,22 @@ List<Plugin> _filterNativePlugins(List<Plugin> plugins, String platformKey) { ...@@ -984,11 +984,22 @@ List<Plugin> _filterNativePlugins(List<Plugin> plugins, String platformKey) {
Future<void> _writeWindowsPluginFiles(FlutterProject project, List<Plugin> plugins) async { Future<void> _writeWindowsPluginFiles(FlutterProject project, List<Plugin> plugins) async {
final List<Plugin>nativePlugins = _filterNativePlugins(plugins, WindowsPlugin.kConfigKey); final List<Plugin>nativePlugins = _filterNativePlugins(plugins, WindowsPlugin.kConfigKey);
final List<Map<String, dynamic>> windowsPlugins = _extractPlatformMaps(nativePlugins, WindowsPlugin.kConfigKey); final List<Map<String, dynamic>> windowsPlugins = _extractPlatformMaps(nativePlugins, WindowsPlugin.kConfigKey);
// The generated file is checked in, so can't use absolute paths. It is
// included by the main CMakeLists.txt, so relative paths must be relative to
// that file's directory.
final String makefileDirPath = project.windows.cmakeFile.parent.absolute.path;
final path.Context cmakePathContext = path.Context(style: path.Style.posix);
final List<String> relativePathComponents = globals.fs.path.split(globals.fs.path.relative(
project.windows.pluginSymlinkDirectory.absolute.path,
from: makefileDirPath,
));
final Map<String, dynamic> context = <String, dynamic>{ final Map<String, dynamic> context = <String, dynamic>{
'os': 'windows',
'plugins': windowsPlugins, 'plugins': windowsPlugins,
'pluginsDir': cmakePathContext.joinAll(relativePathComponents),
}; };
await _writeCppPluginRegistrant(project.windows.managedDirectory, context); await _writeCppPluginRegistrant(project.windows.managedDirectory, context);
await _writeWindowsPluginProperties(project.windows, windowsPlugins); await _writePluginCmakefile(project.windows.generatedPluginCmakeFile, context);
} }
Future<void> _writeCppPluginRegistrant(Directory destination, Map<String, dynamic> templateContext) async { Future<void> _writeCppPluginRegistrant(Directory destination, Map<String, dynamic> templateContext) async {
...@@ -1005,20 +1016,6 @@ Future<void> _writeCppPluginRegistrant(Directory destination, Map<String, dynami ...@@ -1005,20 +1016,6 @@ Future<void> _writeCppPluginRegistrant(Directory destination, Map<String, dynami
); );
} }
Future<void> _writeWindowsPluginProperties(WindowsProject project, List<Map<String, dynamic>> windowsPlugins) async {
final List<String> pluginLibraryFilenames = windowsPlugins.map(
(Map<String, dynamic> plugin) => '${plugin['name']}_plugin.lib').toList();
// Use paths relative to the VS project directory.
final String projectDir = project.vcprojFile.parent.path;
final String symlinkDirPath = project.pluginSymlinkDirectory.path.substring(projectDir.length + 1);
final List<String> pluginIncludePaths = windowsPlugins.map((Map<String, dynamic> plugin) =>
globals.fs.path.join(symlinkDirPath, plugin['name'] as String, 'windows')).toList();
project.generatedPluginPropertySheetFile.writeAsStringSync(PropertySheet(
includePaths: pluginIncludePaths,
libraryDependencies: pluginLibraryFilenames,
).toString());
}
Future<void> _writeWebPluginRegistrant(FlutterProject project, List<Plugin> plugins) async { Future<void> _writeWebPluginRegistrant(FlutterProject project, List<Plugin> plugins) async {
final List<Map<String, dynamic>> webPlugins = _extractPlatformMaps(plugins, WebPlugin.kConfigKey); final List<Map<String, dynamic>> webPlugins = _extractPlatformMaps(plugins, WebPlugin.kConfigKey);
final Map<String, dynamic> context = <String, dynamic>{ final Map<String, dynamic> context = <String, dynamic>{
...@@ -1159,9 +1156,6 @@ Future<void> injectPlugins(FlutterProject project, {bool checkProjects = false}) ...@@ -1159,9 +1156,6 @@ Future<void> injectPlugins(FlutterProject project, {bool checkProjects = false})
} }
if (featureFlags.isWindowsEnabled && project.windows.existsSync()) { if (featureFlags.isWindowsEnabled && project.windows.existsSync()) {
await _writeWindowsPluginFiles(project, plugins); await _writeWindowsPluginFiles(project, plugins);
final List<Plugin> nativePlugins = _filterNativePlugins(plugins, WindowsPlugin.kConfigKey);
await VisualStudioSolutionUtils(project: project.windows, fileSystem: globals.fs).updatePlugins(nativePlugins);
} }
for (final XcodeBasedProject subproject in <XcodeBasedProject>[project.ios, project.macos]) { for (final XcodeBasedProject subproject in <XcodeBasedProject>[project.ios, project.macos]) {
if (!project.isModule && (!checkProjects || subproject.existsSync())) { if (!project.isModule && (!checkProjects || subproject.existsSync())) {
......
...@@ -329,6 +329,27 @@ abstract class XcodeBasedProject { ...@@ -329,6 +329,27 @@ abstract class XcodeBasedProject {
File get podManifestLock; File get podManifestLock;
} }
/// Represents a CMake-based sub-project.
///
/// This defines interfaces common to Windows and Linux projects.
abstract class CmakeBasedProject {
/// The parent of this project.
FlutterProject get parent;
/// Whether the subproject (either Windows or Linux) exists in the Flutter project.
bool existsSync();
/// The native project CMake specification.
File get cmakeFile;
/// Contains definitions for FLUTTER_ROOT, LOCAL_ENGINE, and more flags for
/// the build.
File get generatedCmakeConfigFile;
/// Includable CMake with rules and variables for plugin builds.
File get generatedPluginCmakeFile;
}
/// Represents the iOS sub-project of a Flutter project. /// Represents the iOS sub-project of a Flutter project.
/// ///
/// Instances will reflect the contents of the `ios/` sub-folder of /// Instances will reflect the contents of the `ios/` sub-folder of
...@@ -995,18 +1016,28 @@ class MacOSProject extends FlutterProjectPlatform implements XcodeBasedProject { ...@@ -995,18 +1016,28 @@ class MacOSProject extends FlutterProjectPlatform implements XcodeBasedProject {
} }
/// The Windows sub project /// The Windows sub project
class WindowsProject extends FlutterProjectPlatform { class WindowsProject extends FlutterProjectPlatform implements CmakeBasedProject {
WindowsProject._(this.project); WindowsProject._(this.parent);
final FlutterProject project; @override
final FlutterProject parent;
@override @override
String get pluginConfigKey => WindowsPlugin.kConfigKey; String get pluginConfigKey => WindowsPlugin.kConfigKey;
@override @override
bool existsSync() => _editableDirectory.existsSync() && solutionFile.existsSync(); bool existsSync() => _editableDirectory.existsSync() && cmakeFile.existsSync();
@override
File get cmakeFile => _editableDirectory.childFile('CMakeLists.txt');
@override
File get generatedCmakeConfigFile => ephemeralDirectory.childFile('generated_config.cmake');
@override
File get generatedPluginCmakeFile => managedDirectory.childFile('generated_plugins.cmake');
Directory get _editableDirectory => project.directory.childDirectory('windows'); Directory get _editableDirectory => parent.directory.childDirectory('windows');
/// The directory in the project that is managed by Flutter. As much as /// The directory in the project that is managed by Flutter. As much as
/// possible, files that are edited by Flutter tooling after initial project /// possible, files that are edited by Flutter tooling after initial project
...@@ -1018,24 +1049,6 @@ class WindowsProject extends FlutterProjectPlatform { ...@@ -1018,24 +1049,6 @@ class WindowsProject extends FlutterProjectPlatform {
/// checked in should live here. /// checked in should live here.
Directory get ephemeralDirectory => managedDirectory.childDirectory('ephemeral'); Directory get ephemeralDirectory => managedDirectory.childDirectory('ephemeral');
/// Contains definitions for FLUTTER_ROOT, LOCAL_ENGINE, and more flags for
/// the build.
File get generatedPropertySheetFile => ephemeralDirectory.childFile('Generated.props');
/// Contains configuration to add plugins to the build.
File get generatedPluginPropertySheetFile => managedDirectory.childFile('GeneratedPlugins.props');
// The MSBuild project file.
File get vcprojFile => _editableDirectory.childFile('Runner.vcxproj');
// The MSBuild solution file.
File get solutionFile => _editableDirectory.childFile('Runner.sln');
/// The file where the VS build will write the name of the built app.
///
/// Ideally this will be replaced in the future with inspection of the project.
File get nameFile => ephemeralDirectory.childFile('exe_filename');
/// The directory to write plugin symlinks. /// The directory to write plugin symlinks.
Directory get pluginSymlinkDirectory => ephemeralDirectory.childDirectory('.plugin_symlinks'); Directory get pluginSymlinkDirectory => ephemeralDirectory.childDirectory('.plugin_symlinks');
...@@ -1043,15 +1056,16 @@ class WindowsProject extends FlutterProjectPlatform { ...@@ -1043,15 +1056,16 @@ class WindowsProject extends FlutterProjectPlatform {
} }
/// The Linux sub project. /// The Linux sub project.
class LinuxProject extends FlutterProjectPlatform { class LinuxProject extends FlutterProjectPlatform implements CmakeBasedProject {
LinuxProject._(this.project); LinuxProject._(this.parent);
final FlutterProject project; @override
final FlutterProject parent;
@override @override
String get pluginConfigKey => LinuxPlugin.kConfigKey; String get pluginConfigKey => LinuxPlugin.kConfigKey;
Directory get _editableDirectory => project.directory.childDirectory('linux'); Directory get _editableDirectory => parent.directory.childDirectory('linux');
/// The directory in the project that is managed by Flutter. As much as /// The directory in the project that is managed by Flutter. As much as
/// possible, files that are edited by Flutter tooling after initial project /// possible, files that are edited by Flutter tooling after initial project
...@@ -1066,14 +1080,13 @@ class LinuxProject extends FlutterProjectPlatform { ...@@ -1066,14 +1080,13 @@ class LinuxProject extends FlutterProjectPlatform {
@override @override
bool existsSync() => _editableDirectory.existsSync(); bool existsSync() => _editableDirectory.existsSync();
/// The Linux project CMake specification. @override
File get cmakeFile => _editableDirectory.childFile('CMakeLists.txt'); File get cmakeFile => _editableDirectory.childFile('CMakeLists.txt');
/// Contains definitions for FLUTTER_ROOT, LOCAL_ENGINE, and more flags for @override
/// the build.
File get generatedCmakeConfigFile => ephemeralDirectory.childFile('generated_config.cmake'); File get generatedCmakeConfigFile => ephemeralDirectory.childFile('generated_config.cmake');
/// Includable CMake with rules and variables for plugin builds. @override
File get generatedPluginCmakeFile => managedDirectory.childFile('generated_plugins.cmake'); File get generatedPluginCmakeFile => managedDirectory.childFile('generated_plugins.cmake');
/// The directory to write plugin symlinks. /// The directory to write plugin symlinks.
......
...@@ -5,10 +5,10 @@ ...@@ -5,10 +5,10 @@
import 'package:meta/meta.dart'; import 'package:meta/meta.dart';
import '../application_package.dart'; import '../application_package.dart';
import '../base/common.dart';
import '../base/file_system.dart'; import '../base/file_system.dart';
import '../base/utils.dart'; import '../base/utils.dart';
import '../build_info.dart'; import '../build_info.dart';
import '../cmake.dart';
import '../globals.dart' as globals; import '../globals.dart' as globals;
import '../project.dart'; import '../project.dart';
...@@ -55,24 +55,21 @@ class PrebuiltWindowsApp extends WindowsApp { ...@@ -55,24 +55,21 @@ class PrebuiltWindowsApp extends WindowsApp {
class BuildableWindowsApp extends WindowsApp { class BuildableWindowsApp extends WindowsApp {
BuildableWindowsApp({ BuildableWindowsApp({
@required this.project, @required this.project,
}) : super(projectBundleId: project.project.manifest.appName); }) : super(projectBundleId: project.parent.manifest.appName);
final WindowsProject project; final WindowsProject project;
@override @override
String executable(BuildMode buildMode) { String executable(BuildMode buildMode) {
final File exeNameFile = project.nameFile; final String binaryName = getCmakeExecutableName(project);
if (!exeNameFile.existsSync()) {
throwToolExit('Failed to find Windows executable name');
}
return globals.fs.path.join( return globals.fs.path.join(
getWindowsBuildDirectory(), getWindowsBuildDirectory(),
'x64', 'runner',
toTitleCase(getNameForBuildMode(buildMode)), toTitleCase(getNameForBuildMode(buildMode)),
'Runner', '$binaryName.exe',
exeNameFile.readAsStringSync().trim()); );
} }
@override @override
String get name => project.project.manifest.appName; String get name => project.parent.manifest.appName;
} }
...@@ -10,18 +10,23 @@ import '../base/process.dart'; ...@@ -10,18 +10,23 @@ import '../base/process.dart';
import '../base/utils.dart'; import '../base/utils.dart';
import '../build_info.dart'; import '../build_info.dart';
import '../cache.dart'; import '../cache.dart';
import '../cmake.dart';
import '../globals.dart' as globals; import '../globals.dart' as globals;
import '../plugins.dart'; import '../plugins.dart';
import '../project.dart'; import '../project.dart';
import 'property_sheet.dart';
import 'visual_studio.dart'; import 'visual_studio.dart';
// From https://cmake.org/cmake/help/v3.15/manual/cmake-generators.7.html#visual-studio-generators
// This may need to become a getter on VisualStudio in the future to support
// future major versions of Visual Studio.
const String _cmakeVisualStudioGeneratorIdentifier = 'Visual Studio 16 2019';
/// Builds the Windows project using msbuild. /// Builds the Windows project using msbuild.
Future<void> buildWindows(WindowsProject windowsProject, BuildInfo buildInfo, { Future<void> buildWindows(WindowsProject windowsProject, BuildInfo buildInfo, {
String target, String target,
VisualStudio visualStudioOverride, VisualStudio visualStudioOverride,
}) async { }) async {
if (!windowsProject.solutionFile.existsSync()) { if (!windowsProject.cmakeFile.existsSync()) {
throwToolExit( throwToolExit(
'No Windows desktop project configured. ' 'No Windows desktop project configured. '
'See https://github.com/flutter/flutter/wiki/Desktop-shells#create ' 'See https://github.com/flutter/flutter/wiki/Desktop-shells#create '
...@@ -43,8 +48,8 @@ Future<void> buildWindows(WindowsProject windowsProject, BuildInfo buildInfo, { ...@@ -43,8 +48,8 @@ Future<void> buildWindows(WindowsProject windowsProject, BuildInfo buildInfo, {
} }
// Ensure that necessary emphemeral files are generated and up to date. // Ensure that necessary emphemeral files are generated and up to date.
_writeGeneratedFlutterProperties(windowsProject, buildInfo, target); _writeGeneratedFlutterConfig(windowsProject, buildInfo, target);
createPluginSymlinks(windowsProject.project); createPluginSymlinks(windowsProject.parent);
final VisualStudio visualStudio = visualStudioOverride ?? VisualStudio( final VisualStudio visualStudio = visualStudioOverride ?? VisualStudio(
fileSystem: globals.fs, fileSystem: globals.fs,
...@@ -52,57 +57,88 @@ Future<void> buildWindows(WindowsProject windowsProject, BuildInfo buildInfo, { ...@@ -52,57 +57,88 @@ Future<void> buildWindows(WindowsProject windowsProject, BuildInfo buildInfo, {
logger: globals.logger, logger: globals.logger,
processManager: globals.processManager, processManager: globals.processManager,
); );
final String vcvarsScript = visualStudio.vcvarsPath; final String cmakePath = visualStudio.cmakePath;
if (vcvarsScript == null) { if (cmakePath == null) {
throwToolExit('Unable to find suitable Visual Studio toolchain. ' throwToolExit('Unable to find suitable Visual Studio toolchain. '
'Please run `flutter doctor` for more details.'); 'Please run `flutter doctor` for more details.');
} }
final String buildScript = globals.fs.path.join( final String buildModeName = getNameForBuildMode(buildInfo.mode ?? BuildMode.release);
Cache.flutterRoot, final Directory buildDirectory = globals.fs.directory(getWindowsBuildDirectory());
'packages',
'flutter_tools',
'bin',
'vs_build.bat',
);
final String configuration = toTitleCase(getNameForBuildMode(buildInfo.mode ?? BuildMode.release));
final String solutionPath = windowsProject.solutionFile.path;
final Stopwatch sw = Stopwatch()..start();
final Status status = globals.logger.startProgress( final Status status = globals.logger.startProgress(
'Building Windows application...', 'Building Windows application...',
timeout: null, timeout: null,
); );
try {
await _runCmakeGeneration(cmakePath, buildDirectory, windowsProject.cmakeFile.parent);
await _runBuild(cmakePath, buildDirectory, buildModeName);
} finally {
status.cancel();
}
}
Future<void> _runCmakeGeneration(String cmakePath, Directory buildDir, Directory sourceDir) async {
final Stopwatch sw = Stopwatch()..start();
await buildDir.create(recursive: true);
int result;
try {
result = await processUtils.stream(
<String>[
cmakePath,
'-S',
sourceDir.path,
'-B',
buildDir.path,
'-G',
_cmakeVisualStudioGeneratorIdentifier,
],
trace: true,
);
} on ArgumentError {
throwToolExit("cmake not found. Run 'flutter doctor' for more information.");
}
if (result != 0) {
throwToolExit('Unable to generate build files');
}
globals.flutterUsage.sendTiming('build', 'windows-cmake-generation', Duration(milliseconds: sw.elapsedMilliseconds));
}
Future<void> _runBuild(String cmakePath, Directory buildDir, String buildModeName) async {
final Stopwatch sw = Stopwatch()..start();
int result; int result;
try { try {
// Run the script with a relative path to the project using the enclosing
// directory as the workingDirectory, to avoid hitting the limit on command
// lengths in batch scripts if the absolute path to the project is long.
result = await processUtils.stream( result = await processUtils.stream(
<String>[ <String>[
buildScript, cmakePath,
vcvarsScript, '--build',
globals.fs.path.basename(solutionPath), buildDir.path,
configuration, '--config',
toTitleCase(buildModeName),
'--target',
'INSTALL',
if (globals.logger.isVerbose)
'--verbose'
], ],
environment: <String, String>{ environment: <String, String>{
if (globals.logger.isVerbose) if (globals.logger.isVerbose)
'VERBOSE_SCRIPT_LOGGING': 'true' 'VERBOSE_SCRIPT_LOGGING': 'true'
}, },
workingDirectory: globals.fs.path.dirname(solutionPath),
trace: true, trace: true,
); );
} finally { } on ArgumentError {
status.cancel(); throwToolExit("cmake not found. Run 'flutter doctor' for more information.");
} }
if (result != 0) { if (result != 0) {
throwToolExit('Build process failed. To view the stack trace, please run `flutter run -d windows -v`.'); final String verboseInstructions = globals.logger.isVerbose ? '' : ' To view the stack trace, please run `flutter run -d windows -v`.';
throwToolExit('Build process failed.$verboseInstructions');
} }
globals.flutterUsage.sendTiming('build', 'vs_build', Duration(milliseconds: sw.elapsedMilliseconds)); globals.flutterUsage.sendTiming('build', 'windows-cmake-build', Duration(milliseconds: sw.elapsedMilliseconds));
} }
/// Writes the generatedPropertySheetFile with the configuration for the given build. /// Writes the generated CMake file with the configuration for the given build.
void _writeGeneratedFlutterProperties( void _writeGeneratedFlutterConfig(
WindowsProject windowsProject, WindowsProject windowsProject,
BuildInfo buildInfo, BuildInfo buildInfo,
String target, String target,
...@@ -110,7 +146,7 @@ void _writeGeneratedFlutterProperties( ...@@ -110,7 +146,7 @@ void _writeGeneratedFlutterProperties(
final Map<String, String> environment = <String, String>{ final Map<String, String> environment = <String, String>{
'FLUTTER_ROOT': Cache.flutterRoot, 'FLUTTER_ROOT': Cache.flutterRoot,
'FLUTTER_EPHEMERAL_DIR': windowsProject.ephemeralDirectory.path, 'FLUTTER_EPHEMERAL_DIR': windowsProject.ephemeralDirectory.path,
'PROJECT_DIR': windowsProject.project.directory.path, 'PROJECT_DIR': windowsProject.parent.directory.path,
if (target != null) if (target != null)
'FLUTTER_TARGET': target, 'FLUTTER_TARGET': target,
...buildInfo.toEnvironmentConfig(), ...buildInfo.toEnvironmentConfig(),
...@@ -121,10 +157,7 @@ void _writeGeneratedFlutterProperties( ...@@ -121,10 +157,7 @@ void _writeGeneratedFlutterProperties(
environment['FLUTTER_ENGINE'] = globals.fs.path.dirname(globals.fs.path.dirname(engineOutPath)); environment['FLUTTER_ENGINE'] = globals.fs.path.dirname(globals.fs.path.dirname(engineOutPath));
environment['LOCAL_ENGINE'] = globals.fs.path.basename(engineOutPath); environment['LOCAL_ENGINE'] = globals.fs.path.basename(engineOutPath);
} }
writeGeneratedCmakeConfig(Cache.flutterRoot, windowsProject, environment);
final File propsFile = windowsProject.generatedPropertySheetFile;
propsFile.createSync(recursive: true);
propsFile.writeAsStringSync(PropertySheet(environmentVariables: environment).toString());
} }
// Checks the template version of [project] against the current template // Checks the template version of [project] against the current template
......
// 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:xml/xml.dart' as xml;
/// A utility class for building property sheet (.props) files for use
/// with MSBuild/Visual Studio projects.
class PropertySheet {
/// Creates a PropertySheet with the given properties.
const PropertySheet({
this.environmentVariables,
this.includePaths,
this.libraryDependencies,
});
/// Variables to make available both as build macros and as environment
/// variables for script steps.
final Map<String, String> environmentVariables;
/// Directories to search for headers.
final List<String> includePaths;
/// Libraries to link against.
final List<String> libraryDependencies;
@override
String toString() {
// See https://docs.microsoft.com/en-us/cpp/build/reference/vcxproj-file-structure#property-sheet-layout
final xml.XmlBuilder builder = xml.XmlBuilder();
builder.processing('xml', 'version="1.0" encoding="utf-8"');
builder.element('Project', nest: () {
builder.attribute('ToolsVersion', '4.0');
builder.attribute(
'xmlns', 'http://schemas.microsoft.com/developer/msbuild/2003');
builder.element('ImportGroup', nest: () {
builder.attribute('Label', 'PropertySheets');
});
builder.element('PropertyGroup', nest: () {
builder.attribute('Label', 'UserMacros');
_addEnviromentVariableUserMacros(builder);
});
builder.element('PropertyGroup');
builder.element('ItemDefinitionGroup', nest: () {
_addIncludePaths(builder);
_addLibraryDependencies(builder);
});
builder.element('ItemGroup', nest: () {
_addEnvironmentVariableBuildMacros(builder);
});
});
return builder.build().toXmlString(pretty: true, indent: ' ');
}
/// Adds directories to the header search path.
///
/// Must be called within the context of the ItemDefinitionGroup.
void _addIncludePaths(xml.XmlBuilder builder) {
if (includePaths == null || includePaths.isEmpty) {
return;
}
builder.element('ClCompile', nest: () {
builder.element('AdditionalIncludeDirectories', nest: () {
builder.text('${includePaths.join(';')};%(AdditionalIncludeDirectories)');
});
});
}
/// Adds libraries to the link step.
///
/// Must be called within the context of the ItemDefinitionGroup.
void _addLibraryDependencies(xml.XmlBuilder builder) {
if (libraryDependencies == null || libraryDependencies.isEmpty) {
return;
}
builder.element('Link', nest: () {
builder.element('AdditionalDependencies', nest: () {
builder.text('${libraryDependencies.join(';')};%(AdditionalDependencies)');
});
});
}
/// Writes key/value pairs for any environment variables as user macros.
///
/// Must be called within the context of the UserMacros PropertyGroup.
void _addEnviromentVariableUserMacros(xml.XmlBuilder builder) {
if (environmentVariables == null) {
return;
}
for (final MapEntry<String, String> variable in environmentVariables.entries) {
builder.element(variable.key, nest: () {
builder.text(variable.value);
});
}
}
/// Writes the BuildMacros to expose environment variable UserMacros to the
/// environment.
///
/// Must be called within the context of the ItemGroup.
void _addEnvironmentVariableBuildMacros(xml.XmlBuilder builder) {
if (environmentVariables == null) {
return;
}
for (final String name in environmentVariables.keys) {
builder.element('BuildMacro', nest: () {
builder.attribute('Include', name);
builder.element('Value', nest: () {
builder.text('\$($name)');
});
builder.element('EnvironmentVariable', nest: () {
builder.text('true');
});
});
}
}
}
...@@ -147,20 +147,24 @@ class VisualStudio { ...@@ -147,20 +147,24 @@ class VisualStudio {
return '2019'; return '2019';
} }
/// The path to vcvars64.bat, 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 vcvarsPath { 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;
} }
return _fileSystem.path.join( return _fileSystem.path.joinAll(<String>[
_usableVisualStudioDetails[_installationPathKey] as String, _usableVisualStudioDetails[_installationPathKey] as String,
'VC', 'Common7',
'Auxiliary', 'IDE',
'Build', 'CommonExtensions',
'vcvars64.bat', 'Microsoft',
); 'CMake',
'CMake',
'bin',
'cmake.exe',
]);
} }
/// The major version of the Visual Studio install, as an integer. /// The major version of the Visual Studio install, as an integer.
...@@ -212,6 +216,8 @@ class VisualStudio { ...@@ -212,6 +216,8 @@ class VisualStudio {
return <String, String>{ return <String, String>{
// The C++ toolchain required by the template. // The C++ toolchain required by the template.
'Microsoft.VisualStudio.Component.VC.Tools.x86.x64': cppToolchainDescription, 'Microsoft.VisualStudio.Component.VC.Tools.x86.x64': cppToolchainDescription,
// CMake
'Microsoft.VisualStudio.Component.VC.CMake.Project': 'C++ CMake tools for Windows',
}; };
} }
......
// 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:meta/meta.dart';
import 'package:xml/xml.dart' as xml;
import '../base/file_system.dart';
/// A utility class for interacting with Visual Studio project files (e.g.,
/// .vcxproj).
class VisualStudioProject {
/// Creates a project object from the project file at [file].
VisualStudioProject(this.file, {
@required FileSystem fileSystem,
}): _fileSystem = fileSystem {
try {
content = xml.parse(file.readAsStringSync());
} on xml.XmlParserException {
// Silently continue; formatUnderstood will return false.
}
}
final FileSystem _fileSystem;
/// The file corresponding to this object.
final File file;
/// The content of the project file.
xml.XmlDocument content;
/// Whether or not the project file was correctly parsed.
///
/// If false, this could indicate that the project file is damaged, or that
/// it's an unsupported project type.
bool get formatUnderstood => content != null;
String _guid;
/// Returns the ProjectGuid for the project, or null if it's not present.
String get guid {
return _guid ??= _findGuid();
}
String _findGuid() {
if (!formatUnderstood) {
return null;
}
try {
final String guidValue = content.findAllElements('ProjectGuid').single.text.trim();
// Remove the enclosing {} from the value.
return guidValue.substring(1, guidValue.length - 1);
} on StateError {
// If there is not exactly one ProjectGuid, return null.
return null;
}
}
String _name;
/// Returns the ProjectName for the project.
///
/// If not explicitly set in the project, uses the basename of the project
/// file.
String get name {
return _name ??= _findName();
}
String _findName() {
if (!formatUnderstood) {
return null;
}
try {
return content.findAllElements('ProjectName').first.text.trim();
} on StateError {
// If there is no name, fall back to filename.
return _fileSystem.path.basenameWithoutExtension(file.path);
}
}
}
// 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:meta/meta.dart';
import '../base/common.dart';
import '../base/file_system.dart';
import '../convert.dart';
import '../plugins.dart';
import '../project.dart';
import 'visual_studio_project.dart';
// Constants corresponding to specific reference types in a solution file.
// These values are defined by the .sln format.
const String _kSolutionTypeGuidFolder = '2150E333-8FDC-42A3-9474-1A3956D46DE8';
const String _kSolutionTypeGuidVcxproj = '8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942';
// The GUID for the folder above, managed by this class. This is an arbitrary
// value that was randomly generated, but it should not be changed since that
// would cause issues for existing Flutter projects.
const String _kFlutterPluginSolutionFolderGuid = '5C2E738A-1DD3-445A-AAC8-EEB9648DD07C';
// The FlutterBuild project GUID. This is an arbitrary
// value that was randomly generated, but it should not be changed since that
// would cause issues for existing Flutter projects.
const String _kFlutterBuildProjectGuid = '6419BF13-6ECD-4CD2-9E85-E566A1F03F8F';
/// Extracts and stores the plugin name and vcxproj GUID for [plugin].
class _PluginProjectInfo {
_PluginProjectInfo(Plugin plugin, {
@required FileSystem fileSystem,
}) {
name = plugin.name;
final File projectFile = fileSystem.directory(plugin.path).childDirectory('windows').childFile('plugin.vcxproj');
try {
guid = VisualStudioProject(projectFile, fileSystem: fileSystem).guid;
} on FileSystemException {
throwToolExit('Unable to find a plugin.vcxproj for plugin "$name"');
}
if (guid == null) {
throwToolExit('Unable to find a plugin.vcxproj ID for plugin "$name"');
}
}
// The name of the plugin, which is also the name of the symlink folder.
String name;
// The GUID of the plugin's project.
String guid;
}
// TODO(stuartmorgan): Consider replacing this class with a real parser. See
// https://github.com/flutter/flutter/issues/51430.
class VisualStudioSolutionUtils {
const VisualStudioSolutionUtils({
@required WindowsProject project,
@required FileSystem fileSystem,
}) : _project = project,
_fileSystem = fileSystem;
final WindowsProject _project;
final FileSystem _fileSystem;
/// Updates the solution file for [project] to have the project references and
/// dependencies to include [plugins], removing any previous plugins from the
/// solution.
Future<void> updatePlugins(List<Plugin> plugins) async {
if (!_project.solutionFile.existsSync()) {
throwToolExit(
'Attempted to update Windows plugins on a project that does not '
'support Windows.',
);
}
final String solutionContent = await _project.solutionFile.readAsString();
// Map of GUID to name for the current plugin list.
final Map<String, String> currentPluginInfo = _getWindowsPluginNamesByGuid(plugins);
// Find any plugins referenced in the project that are no longer used, and
// any that are new.
//
// While the simplest approach to updating the solution would be to remove all
// entries associated with plugins, and then add all the current plugins in
// one block, Visual Studio has its own (unknown, likely data-structure-hash
// based) order that it will use each time it writes out the file due to any
// solution-level changes made in the UI. To avoid thrashing, and resulting
// confusion (e.g., in review diffs), this update attempts to instead preserve
// the ordering that is already there, so that once Visual Studio has
// reordered the plugins, that order will be stable.
final Set<String> existingPlugins = _findPreviousPluginGuids(solutionContent);
final Set<String> currentPlugins = currentPluginInfo.keys.toSet();
final Set<String> removedPlugins = existingPlugins.difference(currentPlugins);
final Set<String> addedPlugins = currentPlugins.difference(existingPlugins);
final RegExp projectStartPattern = RegExp(r'^Project\("{' + _kSolutionTypeGuidVcxproj + r'}"\)\s*=\s*".*",\s*"(.*)",\s*"{([A-Fa-f0-9\-]*)}"\s*$');
final RegExp pluginsFolderProjectStartPattern = RegExp(r'^Project\("{' + _kSolutionTypeGuidFolder + r'}"\)\s*=.*"{' + _kFlutterPluginSolutionFolderGuid + r'}"\s*$');
final RegExp projectEndPattern = RegExp(r'^EndProject\s*$');
final RegExp globalStartPattern = RegExp(r'^Global\s*$');
final RegExp globalEndPattern = RegExp(r'^EndGlobal\s*$');
final RegExp projectDependenciesStartPattern = RegExp(r'^\s*ProjectSection\(ProjectDependencies\)\s*=\s*postProject\s*$');
final RegExp globalSectionProjectConfigurationStartPattern = RegExp(r'^\s*GlobalSection\(ProjectConfigurationPlatforms\)\s*=\s*postSolution\s*$');
final RegExp globalSectionNestedProjectsStartPattern = RegExp(r'^\s*GlobalSection\(NestedProjects\)\s*=\s*preSolution\s*$');
final StringBuffer newSolutionContent = StringBuffer();
// readAsString drops the BOM; re-add it.
newSolutionContent.writeCharCode(unicodeBomCharacterRune);
final Iterator<String> lineIterator = solutionContent.split('\n').iterator;
bool foundFlutterPluginsFolder = false;
bool foundNestedProjectsSection = false;
bool foundRunnerProject = false;
while (lineIterator.moveNext()) {
final Match projectStartMatch = projectStartPattern.firstMatch(lineIterator.current);
if (projectStartMatch != null) {
final String guid = projectStartMatch.group(2);
if (currentPlugins.contains(guid)) {
// Write an up-to-date version at this location (in case, e.g., the name
// has changed).
_writePluginProjectEntry(guid, currentPluginInfo[guid], newSolutionContent);
// Drop the old copy.
_skipUntil(lineIterator, projectEndPattern);
continue;
} else if (removedPlugins.contains(guid)) {
// Drop the stale plugin project.
_skipUntil(lineIterator, projectEndPattern);
continue;
} else if (projectStartMatch.group(1) == _project.vcprojFile.basename) {
foundRunnerProject = true;
// Update the Runner project's dependencies on the plugins.
// Skip to the dependencies section, or if there isn't one the end of
// the project.
while (!projectDependenciesStartPattern.hasMatch(lineIterator.current) &&
!projectEndPattern.hasMatch(lineIterator.current)) {
newSolutionContent.writeln(lineIterator.current);
lineIterator.moveNext();
}
// Add/update the dependencies section.
if (projectDependenciesStartPattern.hasMatch(lineIterator.current)) {
newSolutionContent.writeln(lineIterator.current);
_processSectionPluginReferences(removedPlugins, addedPlugins, lineIterator, _writeProjectDependency, newSolutionContent);
} else {
_writeDependenciesSection(currentPlugins, newSolutionContent);
}
}
}
if (pluginsFolderProjectStartPattern.hasMatch(lineIterator.current)) {
foundFlutterPluginsFolder = true;
}
if (globalStartPattern.hasMatch(lineIterator.current)) {
// The Global section is the end of the project list. Add any new plugins
// here, since the location VS will use is unknown. They will likely be
// reordered the next time VS writes the file.
for (final String guid in addedPlugins) {
_writePluginProjectEntry(guid, currentPluginInfo[guid], newSolutionContent);
}
// Also add the plugins folder if there wasn't already one.
if (!foundFlutterPluginsFolder) {
_writePluginFolderProjectEntry(newSolutionContent);
}
}
// Update the ProjectConfiguration section once it is reached.
if (globalSectionProjectConfigurationStartPattern.hasMatch(lineIterator.current)) {
newSolutionContent.writeln(lineIterator.current);
_processSectionPluginReferences(removedPlugins, addedPlugins, lineIterator, _writePluginConfigurationEntries, newSolutionContent);
}
// Update the NestedProjects section once it is reached.
if (globalSectionNestedProjectsStartPattern.hasMatch(lineIterator.current)) {
newSolutionContent.writeln(lineIterator.current);
_processSectionPluginReferences(removedPlugins, addedPlugins, lineIterator, _writePluginNestingEntry, newSolutionContent);
foundNestedProjectsSection = true;
}
// If there wasn't a NestedProjects global section, add one at the end.
if (!foundNestedProjectsSection && globalEndPattern.hasMatch(lineIterator.current)) {
newSolutionContent.writeln('\tGlobalSection(NestedProjects) = preSolution\r');
for (final String guid in currentPlugins) {
_writePluginNestingEntry(guid, newSolutionContent);
}
newSolutionContent.writeln('\tEndGlobalSection\r');
}
// Re-output anything that hasn't been explicitly skipped above.
newSolutionContent.writeln(lineIterator.current);
}
if (!foundRunnerProject) {
throwToolExit(
'Could not add plugins to Windows project:\n'
'Unable to find a "${_project.vcprojFile.basename}" project in ${_project.solutionFile.path}');
}
await _project.solutionFile.writeAsString(newSolutionContent.toString().trimRight());
}
/// Advances [iterator] it reaches an element that matches [pattern].
///
/// Note that the current element at the time of calling is *not* checked.
void _skipUntil(Iterator<String> iterator, RegExp pattern) {
while (iterator.moveNext()) {
if (pattern.hasMatch(iterator.current)) {
return;
}
}
}
/// Writes the main project entry for the plugin with the given [guid] and
/// [name].
void _writePluginProjectEntry(String guid, String name, StringBuffer output) {
output.write('''
Project("{$_kSolutionTypeGuidVcxproj}") = "$name", "Flutter\\ephemeral\\.plugin_symlinks\\$name\\windows\\plugin.vcxproj", "{$guid}"\r
\tProjectSection(ProjectDependencies) = postProject\r
\t\t{$_kFlutterBuildProjectGuid} = {$_kFlutterBuildProjectGuid}\r
\tEndProjectSection\r
EndProject\r
''');
}
/// Writes the main project entry for the Flutter Plugins solution folder.
void _writePluginFolderProjectEntry(StringBuffer output) {
const String folderName = 'Flutter Plugins';
output.write('''
Project("{$_kSolutionTypeGuidFolder}") = "$folderName", "$folderName", "{$_kFlutterPluginSolutionFolderGuid}"\r
EndProject\r
''');
}
/// Writes a project dependencies section, depending on all the GUIDs in
/// [dependencies].
void _writeDependenciesSection(Iterable<String> dependencies, StringBuffer output) {
output.writeln('ProjectSection(ProjectDependencies) = postProject\r');
for (final String guid in dependencies) {
_writeProjectDependency(guid, output);
}
output.writeln('EndProjectSection\r');
}
/// Returns the GUIDs of all the Flutter plugin projects in the given solution.
Set<String> _findPreviousPluginGuids(String solutionContent) {
// Find the plugin folder's known GUID in ProjectDependencies lines.
// Each line in that section has the form:
// {project GUID} = {solution folder GUID}
final RegExp pluginFolderChildrenPattern = RegExp(
r'^\s*{([A-Fa-f0-9\-]*)}\s*=\s*{' + _kFlutterPluginSolutionFolderGuid + r'}\s*$',
multiLine: true,
);
return pluginFolderChildrenPattern
.allMatches(solutionContent)
.map((Match match) => match.group(1)).toSet();
}
/// Returns a mapping of plugin project GUID to name for all the Windows plugins
/// in [plugins].
Map<String, String> _getWindowsPluginNamesByGuid(List<Plugin> plugins) {
final Map<String, String> currentPluginInfo = <String, String>{};
for (final Plugin plugin in plugins) {
if (plugin.platforms.containsKey(_project.pluginConfigKey)) {
final _PluginProjectInfo info = _PluginProjectInfo(plugin, fileSystem: _fileSystem);
if (currentPluginInfo.containsKey(info.guid)) {
throwToolExit('The plugins "${currentPluginInfo[info.guid]}" and "${info.name}" '
'have the same ProjectGuid, which prevents them from being used together.\n\n'
'Please contact the plugin authors to resolve this, and/or remove one of the '
'plugins from your project.');
}
currentPluginInfo[info.guid] = info.name;
}
}
return currentPluginInfo;
}
/// Walks a GlobalSection or ProjectSection, removing entries related to removed
/// plugins and adding entries for new plugins at the end using
/// [newEntryWriter], which takes the guid of the plugin to write entries for.
///
/// The caller is responsible for printing the section start line, which should
/// be [lineIterator.current] when this is called, and the section end line,
/// which will be [lineIterator.current] on return.
void _processSectionPluginReferences(
Set<String> removedPlugins,
Set<String> addedPlugins,
Iterator<String> lineIterator,
Function(String, StringBuffer) newEntryWriter,
StringBuffer output,
) {
// Extracts the guid of the project that a line refers to. Currently all
// sections this function is used for start with "{project guid}", even though
// the rest of the line varies by section, so the pattern can currently be
// shared rather than parameterized.
final RegExp entryPattern = RegExp(r'^\s*{([A-Fa-f0-9\-]*)}');
final RegExp sectionEndPattern = RegExp(r'^\s*End\w*Section\s*$');
while (lineIterator.moveNext()) {
if (sectionEndPattern.hasMatch(lineIterator.current)) {
// The end of the section; add entries for new plugins, then exit.
for (final String guid in addedPlugins) {
newEntryWriter(guid, output);
}
return;
}
// Otherwise it's the sectino body. Drop any lines associated with old
// plugins, but pass everything else through as output.
final Match entryMatch = entryPattern.firstMatch(lineIterator.current);
if (entryMatch != null && removedPlugins.contains(entryMatch.group(1))) {
continue;
}
output.writeln(lineIterator.current);
}
}
/// Writes all the configuration entries for the plugin project with the given
/// [guid].
///
/// Should be called within the context of writing
/// GlobalSection(ProjectConfigurationPlatforms).
void _writePluginConfigurationEntries(String guid, StringBuffer output) {
final List<String> configurations = <String>['Debug', 'Profile', 'Release'];
final List<String> entryTypes = <String>['ActiveCfg', 'Build.0'];
for (final String configuration in configurations) {
for (final String entryType in entryTypes) {
output.writeln('\t\t{$guid}.$configuration|x64.$entryType = $configuration|x64\r');
}
}
}
/// Writes the entries to nest the plugin projects with the given [guid] under
/// the Flutter Plugins solution folder.
///
/// Should be called within the context of writing
/// GlobalSection(NestedProjects).
void _writePluginNestingEntry(String guid, StringBuffer output) {
output.writeln('\t\t{$guid} = {$_kFlutterPluginSolutionFolderGuid}\r');
}
/// Writes the entrie to make a project depend on another project with the
/// given [guid].
///
/// Should be called within the context of writing
/// ProjectSection(ProjectDependencies).
void _writeProjectDependency(String guid, StringBuffer output) {
output.writeln('\t\t{$guid} = {$guid}\r');
}
}
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<TargetName>{{projectName}}</TargetName>
</PropertyGroup>
</Project>
cmake_minimum_required(VERSION 3.15)
project({{projectName}} LANGUAGES CXX)
set(BINARY_NAME "{{projectName}}")
cmake_policy(SET CMP0063 NEW)
set(CMAKE_INSTALL_RPATH "$ORIGIN/lib")
# Configure build options.
get_property(IS_MULTICONFIG GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG)
if(IS_MULTICONFIG)
set(CMAKE_CONFIGURATION_TYPES "Debug;Profile;Release"
CACHE STRING "" FORCE)
else()
if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
set(CMAKE_BUILD_TYPE "Debug" CACHE
STRING "Flutter build mode" FORCE)
set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS
"Debug" "Profile" "Release")
endif()
endif()
set(CMAKE_EXE_LINKER_FLAGS_PROFILE "${CMAKE_EXE_LINKER_FLAGS_RELEASE}")
set(CMAKE_SHARED_LINKER_FLAGS_PROFILE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE}")
set(CMAKE_C_FLAGS_PROFILE "${CMAKE_C_FLAGS_RELEASE}")
set(CMAKE_CXX_FLAGS_PROFILE "${CMAKE_CXX_FLAGS_RELEASE}")
# Use Unicode for all projects.
add_definitions(-DUNICODE -D_UNICODE)
# Compilation settings that should be applied to most targets.
function(APPLY_STANDARD_SETTINGS TARGET)
target_compile_features(${TARGET} PUBLIC cxx_std_17)
target_compile_options(${TARGET} PRIVATE /W4 /WX /wd"4100")
target_compile_options(${TARGET} PRIVATE /EHsc)
target_compile_definitions(${TARGET} PRIVATE "_HAS_EXCEPTIONS=0")
target_compile_definitions(${TARGET} PRIVATE "$<$<CONFIG:Debug>:_DEBUG>")
endfunction()
set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter")
# Flutter library and tool build rules.
add_subdirectory(${FLUTTER_MANAGED_DIR})
# Application build
add_subdirectory("runner")
# Generated plugin build rules, which manage building the plugins and adding
# them to the application.
include(flutter/generated_plugins.cmake)
# === Installation ===
# Support files are copied into place next to the executable, so that it can
# run in place. This is done instead of making a separate bundle (as on Linux)
# so that building and running from within Visual Studio will work.
set(BUILD_BUNDLE_DIR "$<TARGET_FILE_DIR:${BINARY_NAME}>")
# Make the "install" step default, as it's required to run.
set(CMAKE_VS_INCLUDE_INSTALL_TO_DEFAULT_BUILD 1)
if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT)
set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE)
endif()
set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data")
set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}")
install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}"
COMPONENT Runtime)
install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}"
COMPONENT Runtime)
install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
COMPONENT Runtime)
if(PLUGIN_BUNDLED_LIBRARIES)
install(FILES "${PLUGIN_BUNDLED_LIBRARIES}"
DESTINATION "${INSTALL_BUNDLE_LIB_DIR}"
COMPONENT Runtime)
endif()
# 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}\")
" COMPONENT Runtime)
install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}"
DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime)
# Install the AOT library on non-Debug builds only.
install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}"
CONFIGURATIONS Profile;Release
COMPONENT Runtime)
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup Label="ProjectConfigurations">
<ProjectConfiguration Include="Debug|x64">
<Configuration>Debug</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Profile|x64">
<Configuration>Profile</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|x64">
<Configuration>Release</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
</ItemGroup>
<PropertyGroup Label="Globals">
<VCProjectVersion>15.0</VCProjectVersion>
<ProjectGuid>{6419BF13-6ECD-4CD2-9E85-E566A1F03F8F}</ProjectGuid>
<ProjectName>Flutter Build</ProjectName>
<WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<PropertyGroup Label="Configuration">
<PlatformToolset Condition="'$(VisualStudioVersion)' == '15.0'">v141</PlatformToolset>
<PlatformToolset Condition="'$(VisualStudioVersion)' == '16.0'">v142</PlatformToolset>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
<ImportGroup Label="ExtensionSettings">
</ImportGroup>
<ImportGroup Label="Shared">
</ImportGroup>
<ImportGroup Label="PropertySheets">
<Import Project="flutter\ephemeral\Generated.props" />
</ImportGroup>
<PropertyGroup Label="UserMacros" />
<PropertyGroup />
<ItemDefinitionGroup>
<CustomBuildStep>
<Command>"$(ProjectDir)scripts\prepare_dependencies" $(Configuration)</Command>
<Message>Running Flutter backend build</Message>
<Outputs>force_to_run_every_time</Outputs>
</CustomBuildStep>
</ItemDefinitionGroup>
<ItemGroup>
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
</ImportGroup>
</Project>

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16
VisualStudioVersion = 16.0.29709.97
MinimumVisualStudioVersion = 10.0.40219.1
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Runner", "Runner.vcxproj", "{5A827760-CF8B-408A-99A3-B6C0AD2271E7}"
ProjectSection(ProjectDependencies) = postProject
{6419BF13-6ECD-4CD2-9E85-E566A1F03F8F} = {6419BF13-6ECD-4CD2-9E85-E566A1F03F8F}
EndProjectSection
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Flutter Build", "FlutterBuild.vcxproj", "{6419BF13-6ECD-4CD2-9E85-E566A1F03F8F}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|x64 = Debug|x64
Profile|x64 = Profile|x64
Release|x64 = Release|x64
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{5A827760-CF8B-408A-99A3-B6C0AD2271E7}.Debug|x64.ActiveCfg = Debug|x64
{5A827760-CF8B-408A-99A3-B6C0AD2271E7}.Debug|x64.Build.0 = Debug|x64
{5A827760-CF8B-408A-99A3-B6C0AD2271E7}.Profile|x64.ActiveCfg = Profile|x64
{5A827760-CF8B-408A-99A3-B6C0AD2271E7}.Profile|x64.Build.0 = Profile|x64
{5A827760-CF8B-408A-99A3-B6C0AD2271E7}.Release|x64.ActiveCfg = Release|x64
{5A827760-CF8B-408A-99A3-B6C0AD2271E7}.Release|x64.Build.0 = Release|x64
{6419BF13-6ECD-4CD2-9E85-E566A1F03F8F}.Debug|x64.ActiveCfg = Debug|x64
{6419BF13-6ECD-4CD2-9E85-E566A1F03F8F}.Debug|x64.Build.0 = Debug|x64
{6419BF13-6ECD-4CD2-9E85-E566A1F03F8F}.Profile|x64.ActiveCfg = Profile|x64
{6419BF13-6ECD-4CD2-9E85-E566A1F03F8F}.Profile|x64.Build.0 = Profile|x64
{6419BF13-6ECD-4CD2-9E85-E566A1F03F8F}.Release|x64.ActiveCfg = Release|x64
{6419BF13-6ECD-4CD2-9E85-E566A1F03F8F}.Release|x64.Build.0 = Release|x64
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {B8A69CB0-A974-4774-9EBD-1E5EECACD186}
EndGlobalSection
EndGlobal
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<Filter Include="Source Files">
<UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>
<Extensions>cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx</Extensions>
</Filter>
<Filter Include="Header Files">
<UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier>
<Extensions>h;hh;hpp;hxx;hm;inl;inc;ipp;xsd</Extensions>
</Filter>
<Filter Include="Resource Files">
<UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier>
<Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions>
</Filter>
<Filter Include="Source Files\Client Wrapper">
<UniqueIdentifier>{2761a4b5-57b2-4d50-a677-d20ddc17a7f1}</UniqueIdentifier>
</Filter>
</ItemGroup>
<ItemGroup>
<ClCompile Include="runner\main.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="runner\run_loop.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="runner\flutter_window.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="runner\utils.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="runner\win32_window.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="runner\window_configuration.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="flutter\generated_plugin_registrant.cc">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="$(FLUTTER_EPHEMERAL_DIR)\cpp_client_wrapper\engine_method_result.cc">
<Filter>Source Files\Client Wrapper</Filter>
</ClCompile>
<ClCompile Include="$(FLUTTER_EPHEMERAL_DIR)\cpp_client_wrapper\flutter_view_controller.cc">
<Filter>Source Files\Client Wrapper</Filter>
</ClCompile>
<ClCompile Include="$(FLUTTER_EPHEMERAL_DIR)\cpp_client_wrapper\plugin_registrar.cc">
<Filter>Source Files\Client Wrapper</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="runner\run_loop.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="runner\flutter_window.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="runner\utils.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="runner\win32_window.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="runner\window_configuration.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="runner\resource.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="flutter\generated_plugin_registrant.h">
<Filter>Header Files</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<Manifest Include="runner.exe.manifest" />
</ItemGroup>
<ItemGroup>
<ResourceCompile Include="Runner.rc">
<Filter>Resource Files</Filter>
</ResourceCompile>
</ItemGroup>
<ItemGroup>
<Image Include="resources\app_icon.ico">
<Filter>Resource Files</Filter>
</Image>
</ItemGroup>
</Project>
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup Label="ProjectConfigurations">
<ProjectConfiguration Include="Debug|x64">
<Configuration>Debug</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Profile|x64">
<Configuration>Profile</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|x64">
<Configuration>Release</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
</ItemGroup>
<PropertyGroup Label="Globals">
<VCProjectVersion>15.0</VCProjectVersion>
<ProjectGuid>{5A827760-CF8B-408A-99A3-B6C0AD2271E7}</ProjectGuid>
<RootNamespace>{{projectName}}</RootNamespace>
<WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<PlatformToolset Condition="'$(VisualStudioVersion)' == '15.0'">v141</PlatformToolset>
<PlatformToolset Condition="'$(VisualStudioVersion)' == '16.0'">v142</PlatformToolset>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
<PlatformToolset Condition="'$(VisualStudioVersion)' == '15.0'">v141</PlatformToolset>
<PlatformToolset Condition="'$(VisualStudioVersion)' == '16.0'">v142</PlatformToolset>
<WholeProgramOptimization>true</WholeProgramOptimization>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Profile|x64'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
<PlatformToolset Condition="'$(VisualStudioVersion)' == '15.0'">v141</PlatformToolset>
<PlatformToolset Condition="'$(VisualStudioVersion)' == '16.0'">v142</PlatformToolset>
<WholeProgramOptimization>true</WholeProgramOptimization>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
<ImportGroup Label="ExtensionSettings">
</ImportGroup>
<ImportGroup Label="Shared">
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
<Import Project="flutter\ephemeral\Generated.props" />
<Import Project="AppConfiguration.props" />
<Import Project="flutter\GeneratedPlugins.props" />
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
<Import Project="flutter\ephemeral\Generated.props" />
<Import Project="AppConfiguration.props" />
<Import Project="flutter\GeneratedPlugins.props" />
</ImportGroup>
<ImportGroup Condition="'$(Configuration)|$(Platform)'=='Profile|x64'" Label="PropertySheets">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
<Import Project="flutter\ephemeral\Generated.props" />
<Import Project="AppConfiguration.props" />
<Import Project="flutter\GeneratedPlugins.props" />
</ImportGroup>
<PropertyGroup Label="UserMacros" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<OutDir>$(ProjectDir)..\build\windows\$(Platform)\$(Configuration)\$(ProjectName)\</OutDir>
<IntDir>$(ProjectDir)..\build\windows\intermediates\$(Platform)\$(Configuration)\$(ProjectName)\</IntDir>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<OutDir>$(ProjectDir)..\build\windows\$(Platform)\$(Configuration)\$(ProjectName)\</OutDir>
<IntDir>$(ProjectDir)..\build\windows\intermediates\$(Platform)\$(Configuration)\$(ProjectName)\</IntDir>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Profile|x64'">
<OutDir>$(ProjectDir)..\build\windows\$(Platform)\$(Configuration)\$(ProjectName)\</OutDir>
<IntDir>$(ProjectDir)..\build\windows\intermediates\$(Platform)\$(Configuration)\$(ProjectName)\</IntDir>
</PropertyGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<ClCompile>
<WarningLevel>Level4</WarningLevel>
<Optimization>Disabled</Optimization>
<SDLCheck>true</SDLCheck>
<ConformanceMode>true</ConformanceMode>
<AdditionalIncludeDirectories>$(ProjectDir);$(FLUTTER_EPHEMERAL_DIR);$(FLUTTER_EPHEMERAL_DIR)\cpp_client_wrapper\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<PreprocessorDefinitions>_MBCS;_HAS_EXCEPTIONS=0;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<TreatWarningAsError>true</TreatWarningAsError>
<DisableSpecificWarnings>4100</DisableSpecificWarnings>
</ClCompile>
<Link>
<AdditionalDependencies>flutter_windows.dll.lib;%(AdditionalDependencies)</AdditionalDependencies>
<AdditionalLibraryDirectories>$(FLUTTER_EPHEMERAL_DIR);$(OutDir)..\Plugins;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
</Link>
<PreBuildEvent>
<Command>
</Command>
<Message>
</Message>
</PreBuildEvent>
<PreLinkEvent>
<Command>
</Command>
<Message>
</Message>
</PreLinkEvent>
<PostBuildEvent>
<Command>
</Command>
</PostBuildEvent>
<PostBuildEvent>
<Message>
</Message>
</PostBuildEvent>
<CustomBuildStep>
<Command>"$(ProjectDir)scripts\bundle_assets_and_deps" "$(FLUTTER_EPHEMERAL_DIR)\" "$(OutputPath)" "$(OutputPath)..\Plugins\" "$(TargetFileName)" "$(Configuration)"</Command>
</CustomBuildStep>
<CustomBuildStep>
<Message>Bundling dependencies</Message>
<Outputs>Dummy_Run_Always</Outputs>
<Inputs>
</Inputs>
</CustomBuildStep>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<ClCompile>
<WarningLevel>Level4</WarningLevel>
<Optimization>MaxSpeed</Optimization>
<FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>true</IntrinsicFunctions>
<SDLCheck>true</SDLCheck>
<ConformanceMode>true</ConformanceMode>
<AdditionalIncludeDirectories>$(ProjectDir);$(FLUTTER_EPHEMERAL_DIR);$(FLUTTER_EPHEMERAL_DIR)\cpp_client_wrapper\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<PreprocessorDefinitions>_MBCS;_HAS_EXCEPTIONS=0;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<TreatWarningAsError>true</TreatWarningAsError>
<DisableSpecificWarnings>4100</DisableSpecificWarnings>
</ClCompile>
<Link>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<OptimizeReferences>true</OptimizeReferences>
<AdditionalDependencies>flutter_windows.dll.lib;%(AdditionalDependencies)</AdditionalDependencies>
<AdditionalLibraryDirectories>$(FLUTTER_EPHEMERAL_DIR);$(OutDir)..\Plugins;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
</Link>
<PreBuildEvent>
<Command>
</Command>
<Message>
</Message>
</PreBuildEvent>
<PreLinkEvent>
<Command>
</Command>
<Message>
</Message>
</PreLinkEvent>
<PostBuildEvent>
<Command>
</Command>
</PostBuildEvent>
<PostBuildEvent>
<Message>
</Message>
</PostBuildEvent>
<CustomBuildStep>
<Command>"$(ProjectDir)scripts\bundle_assets_and_deps" "$(FLUTTER_EPHEMERAL_DIR)\" "$(OutputPath)" "$(OutputPath)..\Plugins\" "$(TargetFileName)" "$(Configuration)"</Command>
</CustomBuildStep>
<CustomBuildStep>
<Message>Bundling dependencies</Message>
<Outputs>Dummy_Run_Always</Outputs>
<Inputs>
</Inputs>
</CustomBuildStep>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Profile|x64'">
<ClCompile>
<WarningLevel>Level4</WarningLevel>
<Optimization>MaxSpeed</Optimization>
<FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>true</IntrinsicFunctions>
<SDLCheck>true</SDLCheck>
<ConformanceMode>true</ConformanceMode>
<AdditionalIncludeDirectories>$(ProjectDir);$(FLUTTER_EPHEMERAL_DIR);$(FLUTTER_EPHEMERAL_DIR)\cpp_client_wrapper\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<PreprocessorDefinitions>_MBCS;_HAS_EXCEPTIONS=0;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<TreatWarningAsError>true</TreatWarningAsError>
<DisableSpecificWarnings>4100</DisableSpecificWarnings>
</ClCompile>
<Link>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<OptimizeReferences>true</OptimizeReferences>
<AdditionalDependencies>flutter_windows.dll.lib;%(AdditionalDependencies)</AdditionalDependencies>
<AdditionalLibraryDirectories>$(FLUTTER_EPHEMERAL_DIR);$(OutDir)..\Plugins;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
</Link>
<PreBuildEvent>
<Command>
</Command>
<Message>
</Message>
</PreBuildEvent>
<PreLinkEvent>
<Command>
</Command>
<Message>
</Message>
</PreLinkEvent>
<PostBuildEvent>
<Command>
</Command>
</PostBuildEvent>
<PostBuildEvent>
<Message>
</Message>
</PostBuildEvent>
<CustomBuildStep>
<Command>"$(ProjectDir)scripts\bundle_assets_and_deps" "$(FLUTTER_EPHEMERAL_DIR)\" "$(OutputPath)" "$(OutputPath)..\Plugins\" "$(TargetFileName)" "$(Configuration)"</Command>
</CustomBuildStep>
<CustomBuildStep>
<Message>Bundling dependencies</Message>
<Outputs>Dummy_Run_Always</Outputs>
<Inputs>
</Inputs>
</CustomBuildStep>
</ItemDefinitionGroup>
<ItemGroup>
<ClCompile Include="runner\main.cpp" />
<ClCompile Include="flutter\generated_plugin_registrant.cc" />
<ClCompile Include="runner\run_loop.cpp" />
<ClCompile Include="runner\utils.cpp" />
<ClCompile Include="runner\window_configuration.cpp" />
<ClCompile Include="runner\win32_window.cpp" />
<ClCompile Include="runner\flutter_window.cpp" />
<ClCompile Include="$(FLUTTER_EPHEMERAL_DIR)\cpp_client_wrapper\engine_method_result.cc" />
<ClCompile Include="$(FLUTTER_EPHEMERAL_DIR)\cpp_client_wrapper\flutter_view_controller.cc" />
<ClCompile Include="$(FLUTTER_EPHEMERAL_DIR)\cpp_client_wrapper\plugin_registrar.cc" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="flutter\generated_plugin_registrant.h" />
<ClInclude Include="runner\resource.h" />
<ClInclude Include="runner\run_loop.h" />
<ClInclude Include="runner\utils.h" />
<ClInclude Include="runner\win32_window.h" />
<ClInclude Include="runner\flutter_window.h" />
<ClInclude Include="runner\window_configuration.h" />
</ItemGroup>
<ItemGroup>
<Manifest Include="runner\runner.exe.manifest" />
</ItemGroup>
<ItemGroup>
<ResourceCompile Include="runner\Runner.rc" />
</ItemGroup>
<ItemGroup>
<Image Include="runner\resources\app_icon.ico" />
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
</ImportGroup>
<PropertyGroup>
<LocalDebuggerWorkingDirectory>$(SolutionDir)</LocalDebuggerWorkingDirectory>
</PropertyGroup>
</Project>
cmake_minimum_required(VERSION 3.15)
set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral")
# Configuration provided via flutter tool.
include(${EPHEMERAL_DIR}/generated_config.cmake)
# TODO: Move the rest of this into files in ephemeral. See
# https://github.com/flutter/flutter/issues/57146.
set(WRAPPER_ROOT "${EPHEMERAL_DIR}/cpp_client_wrapper")
# === Flutter Library ===
set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/flutter_windows.dll")
# Published to parent scope for install step.
set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE)
set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE)
set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE)
set(AOT_LIBRARY "${PROJECT_DIR}/build/windows/app.so" PARENT_SCOPE)
list(APPEND FLUTTER_LIBRARY_HEADERS
"flutter_export.h"
"flutter_windows.h"
"flutter_messenger.h"
"flutter_plugin_registrar.h"
)
list(TRANSFORM FLUTTER_LIBRARY_HEADERS PREPEND "${EPHEMERAL_DIR}/")
add_library(flutter INTERFACE)
target_include_directories(flutter INTERFACE
"${EPHEMERAL_DIR}"
)
target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}.lib")
add_dependencies(flutter flutter_assemble)
# === Wrapper ===
list(APPEND CPP_WRAPPER_SOURCES_CORE
"engine_method_result.cc"
"standard_codec.cc"
)
list(TRANSFORM CPP_WRAPPER_SOURCES_CORE PREPEND "${WRAPPER_ROOT}/")
list(APPEND CPP_WRAPPER_SOURCES_PLUGIN
"plugin_registrar.cc"
)
list(TRANSFORM CPP_WRAPPER_SOURCES_PLUGIN PREPEND "${WRAPPER_ROOT}/")
list(APPEND CPP_WRAPPER_SOURCES_APP
"flutter_view_controller.cc"
)
list(TRANSFORM CPP_WRAPPER_SOURCES_APP PREPEND "${WRAPPER_ROOT}/")
# Wrapper sources needed for a plugin.
add_library(flutter_wrapper_plugin STATIC
${CPP_WRAPPER_SOURCES_CORE}
${CPP_WRAPPER_SOURCES_PLUGIN}
)
apply_standard_settings(flutter_wrapper_plugin)
set_target_properties(flutter_wrapper_plugin PROPERTIES
POSITION_INDEPENDENT_CODE ON)
set_target_properties(flutter_wrapper_plugin PROPERTIES
CXX_VISIBILITY_PRESET hidden)
target_link_libraries(flutter_wrapper_plugin PUBLIC flutter)
target_include_directories(flutter_wrapper_plugin PUBLIC
"${WRAPPER_ROOT}/include"
)
add_dependencies(flutter_wrapper_plugin flutter_assemble)
# Wrapper sources needed for the runner.
add_library(flutter_wrapper_app STATIC
${CPP_WRAPPER_SOURCES_CORE}
${CPP_WRAPPER_SOURCES_APP}
)
apply_standard_settings(flutter_wrapper_app)
target_link_libraries(flutter_wrapper_app PUBLIC flutter)
target_include_directories(flutter_wrapper_app PUBLIC
"${WRAPPER_ROOT}/include"
)
add_dependencies(flutter_wrapper_app flutter_assemble)
# === Flutter tool backend ===
# _phony_ is a non-existent file to force this command to run every time,
# since currently there's no way to get a full input/output list from the
# flutter tool.
add_custom_command(
OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS}
${CPP_WRAPPER_SOURCES_CORE} ${CPP_WRAPPER_SOURCES_PLUGIN}
${CPP_WRAPPER_SOURCES_APP}
${CMAKE_CURRENT_BINARY_DIR}/_phony_
COMMAND ${CMAKE_COMMAND} -E env
${FLUTTER_TOOL_ENVIRONMENT}
"${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.bat"
windows-x64 $<CONFIG>
)
add_custom_target(flutter_assemble DEPENDS
"${FLUTTER_LIBRARY}"
${FLUTTER_LIBRARY_HEADERS}
${CPP_WRAPPER_SOURCES_CORE}
${CPP_WRAPPER_SOURCES_PLUGIN}
${CPP_WRAPPER_SOURCES_APP}
)
cmake_minimum_required(VERSION 3.15)
project(runner LANGUAGES CXX)
add_executable(${BINARY_NAME} WIN32
"flutter_window.cpp"
"main.cpp"
"run_loop.cpp"
"utils.cpp"
"win32_window.cpp"
"window_configuration.cpp"
"${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc"
"Runner.rc"
"runner.exe.manifest"
)
apply_standard_settings(${BINARY_NAME})
target_link_libraries(${BINARY_NAME} PRIVATE flutter flutter_wrapper_app)
target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}")
add_dependencies(${BINARY_NAME} flutter_assemble)
@echo off
set FLUTTER_CACHE_DIR=%~1
set BUNDLE_DIR=%~2
set PLUGIN_DIR=%~3
set EXE_NAME=%~4
set BUILD_MODE=%~5
set DATA_DIR=%BUNDLE_DIR%data
if not exist "%DATA_DIR%" call mkdir "%DATA_DIR%"
if %errorlevel% neq 0 exit /b %errorlevel%
:: Write the executable name to the location expected by the Flutter tool.
echo %EXE_NAME%>"%FLUTTER_CACHE_DIR%exe_filename"
:: Copy the Flutter assets to the data directory.
set FLUTTER_BUILD_DIR=%~dp0..\..\build\
set ASSET_DIR_NAME=flutter_assets
set TARGET_ASSET_DIR=%DATA_DIR%\%ASSET_DIR_NAME%
if exist "%TARGET_ASSET_DIR%" call rmdir /s /q "%TARGET_ASSET_DIR%"
if %errorlevel% neq 0 exit /b %errorlevel%
call xcopy /s /e /i /q "%FLUTTER_BUILD_DIR%%ASSET_DIR_NAME%" "%TARGET_ASSET_DIR%"
if %errorlevel% neq 0 exit /b %errorlevel%
:: Copy the icudtl.dat file from the Flutter tree to the data directory.
call xcopy /y /d /q "%FLUTTER_CACHE_DIR%icudtl.dat" "%DATA_DIR%"
if %errorlevel% neq 0 exit /b %errorlevel%
:: For non-debug modes, copy app.so into the data directory.
if not %BUILD_MODE% == "Debug" (
call xcopy /y /d /q "%FLUTTER_BUILD_DIR%windows\app.so" "%DATA_DIR%"
if %errorlevel% neq 0 exit /b %errorlevel%
)
:: Copy the Flutter DLL to the target location.
call xcopy /y /d /q "%FLUTTER_CACHE_DIR%flutter_windows.dll" "%BUNDLE_DIR%"
if %errorlevel% neq 0 exit /b %errorlevel%
:: Copy any Plugin DLLs to the target location.
if exist "%PLUGIN_DIR%" (
call xcopy /y /d /q "%PLUGIN_DIR%"*.dll "%BUNDLE_DIR%"
if %errorlevel% neq 0 exit /b %errorlevel%
)
@echo off
:: Run flutter tool backend.
set BUILD_MODE=%~1
"%FLUTTER_ROOT%\packages\flutter_tools\bin\tool_backend" windows-x64 %BUILD_MODE%
cmake_minimum_required(VERSION 3.15)
set(PROJECT_NAME "{{projectName}}")
project(${PROJECT_NAME} LANGUAGES CXX)
set(PLUGIN_NAME "${PROJECT_NAME}_plugin")
add_library(${PLUGIN_NAME} SHARED
"${PLUGIN_NAME}.cpp"
)
apply_standard_settings(${PLUGIN_NAME})
set_target_properties(${PLUGIN_NAME} PROPERTIES
CXX_VISIBILITY_PRESET hidden)
target_compile_definitions(${PLUGIN_NAME} PRIVATE FLUTTER_PLUGIN_IMPL)
target_include_directories(${PLUGIN_NAME} INTERFACE
"${CMAKE_CURRENT_SOURCE_DIR}/include")
target_link_libraries(${PLUGIN_NAME} PRIVATE flutter flutter_wrapper_plugin)
# List of absolute paths to libraries that should be bundled with the plugin
set({{projectName}}_bundled_libraries
""
PARENT_SCOPE
)
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ImportGroup Label="PropertySheets" />
<PropertyGroup Label="UserMacros">
<FlutterPluginName>{{projectName}}</FlutterPluginName>
</PropertyGroup>
<PropertyGroup />
<ItemDefinitionGroup />
<ItemGroup>
<BuildMacro Include="FlutterPluginName">
<Value>$(FlutterPluginName)</Value>
</BuildMacro>
</ItemGroup>
</Project>
#ifndef FLUTTER_PLUGIN_{{pluginClassCapitalSnakeCase}}_H_ #ifndef FLUTTER_PLUGIN_{{pluginClassCapitalSnakeCase}}_H_
#defineFLUTTER_PLUGIN_{{pluginClassCapitalSnakeCase}}_H_ #define FLUTTER_PLUGIN_{{pluginClassCapitalSnakeCase}}_H_
#include <flutter_plugin_registrar.h> #include <flutter_plugin_registrar.h>
......
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<Filter Include="Source Files">
<UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>
<Extensions>cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx</Extensions>
</Filter>
<Filter Include="Header Files">
<UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier>
<Extensions>h;hh;hpp;hxx;hm;inl;inc;ipp;xsd</Extensions>
</Filter>
<Filter Include="Resource Files">
<UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier>
<Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions>
</Filter>
<Filter Include="Source Files\cpp_client_wrapper">
<UniqueIdentifier>{dbe2dac9-4a21-4849-bef5-2069d695609d}</UniqueIdentifier>
</Filter>
</ItemGroup>
<ItemGroup>
<ClInclude Include="stdafx.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="targetver.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="$(FlutterPluginName)_plugin.h">
<Filter>Source Files</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<ClCompile Include="$(FLUTTER_EPHEMERAL_DIR)\cpp_client_wrapper\plugin_registrar.cc">
<Filter>Source Files\cpp_client_wrapper</Filter>
</ClCompile>
<ClCompile Include="$(FLUTTER_EPHEMERAL_DIR)\cpp_client_wrapper\standard_codec.cc">
<Filter>Source Files\cpp_client_wrapper</Filter>
</ClCompile>
<ClCompile Include="$(FLUTTER_EPHEMERAL_DIR)\cpp_client_wrapper\engine_method_result.cc">
<Filter>Source Files\cpp_client_wrapper</Filter>
</ClCompile>
<ClCompile Include="$(FlutterPluginName)_plugin.cpp">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup>
</Project>
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup Label="ProjectConfigurations">
<ProjectConfiguration Include="Debug|x64">
<Configuration>Debug</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Profile|x64">
<Configuration>Profile</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|x64">
<Configuration>Release</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
</ItemGroup>
<PropertyGroup Label="Globals">
<VCProjectVersion>15.0</VCProjectVersion>
<ProjectGuid>{{{pluginProjectUUID}}}</ProjectGuid>
<Keyword>Win32Proj</Keyword>
<RootNamespace>{{projectName}}</RootNamespace>
<WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>
<ProjectName>{{projectName}}</ProjectName>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
<ConfigurationType>DynamicLibrary</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<PlatformToolset Condition="'$(VisualStudioVersion)' == '15.0'">v141</PlatformToolset>
<PlatformToolset Condition="'$(VisualStudioVersion)' == '16.0'">v142</PlatformToolset>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
<ConfigurationType>DynamicLibrary</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
<PlatformToolset Condition="'$(VisualStudioVersion)' == '15.0'">v141</PlatformToolset>
<PlatformToolset Condition="'$(VisualStudioVersion)' == '16.0'">v142</PlatformToolset>
<WholeProgramOptimization>true</WholeProgramOptimization>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Profile|x64'" Label="Configuration">
<ConfigurationType>DynamicLibrary</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
<PlatformToolset Condition="'$(VisualStudioVersion)' == '15.0'">v141</PlatformToolset>
<PlatformToolset Condition="'$(VisualStudioVersion)' == '16.0'">v142</PlatformToolset>
<WholeProgramOptimization>true</WholeProgramOptimization>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
<ImportGroup Label="ExtensionSettings">
</ImportGroup>
<ImportGroup Label="Shared">
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
<Import Project="$(SolutionDir)flutter\ephemeral\Generated.props" />
<Import Project="PluginInfo.props" />
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
<Import Project="$(SolutionDir)flutter\ephemeral\Generated.props" />
<Import Project="PluginInfo.props" />
</ImportGroup>
<ImportGroup Condition="'$(Configuration)|$(Platform)'=='Profile|x64'" Label="PropertySheets">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
<Import Project="$(SolutionDir)flutter\ephemeral\Generated.props" />
<Import Project="PluginInfo.props" />
</ImportGroup>
<PropertyGroup Label="UserMacros" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<LinkIncremental>true</LinkIncremental>
<OutDir>$(SolutionDir)..\build\windows\$(Platform)\$(Configuration)\Plugins\</OutDir>
<IntDir>$(SolutionDir)..\build\windows\intermediates\$(Platform)\$(Configuration)\$(ProjectName)\</IntDir>
<CustomBuildBeforeTargets>
</CustomBuildBeforeTargets>
<TargetName>$(ProjectName)_plugin</TargetName>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<LinkIncremental>false</LinkIncremental>
<OutDir>$(SolutionDir)..\build\windows\$(Platform)\$(Configuration)\Plugins\</OutDir>
<IntDir>$(SolutionDir)..\build\windows\intermediates\$(Platform)\$(Configuration)\$(ProjectName)\</IntDir>
<CustomBuildBeforeTargets>
</CustomBuildBeforeTargets>
<TargetName>$(ProjectName)_plugin</TargetName>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Profile|x64'">
<LinkIncremental>false</LinkIncremental>
<OutDir>$(SolutionDir)..\build\windows\$(Platform)\$(Configuration)\Plugins\</OutDir>
<IntDir>$(SolutionDir)..\build\windows\intermediates\$(Platform)\$(Configuration)\$(ProjectName)\</IntDir>
<CustomBuildBeforeTargets />
<TargetName>$(ProjectName)_plugin</TargetName>
</PropertyGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<ClCompile>
<PrecompiledHeader>NotUsing</PrecompiledHeader>
<WarningLevel>Level4</WarningLevel>
<Optimization>Disabled</Optimization>
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>_DEBUG;FLUTTER_PLUGIN_IMPL;_WINDOWS;_USRDLL;_HAS_EXCEPTIONS=0;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
<ExceptionHandling>false</ExceptionHandling>
<TreatWarningAsError>true</TreatWarningAsError>
<DisableSpecificWarnings>4100</DisableSpecificWarnings>
<AdditionalIncludeDirectories>$(FLUTTER_EPHEMERAL_DIR);$(FLUTTER_EPHEMERAL_DIR)\cpp_client_wrapper\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
</ClCompile>
<Link>
<SubSystem>Windows</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation>
<AdditionalDependencies>flutter_windows.dll.lib;%(AdditionalDependencies)</AdditionalDependencies>
<AdditionalLibraryDirectories>$(FLUTTER_EPHEMERAL_DIR);%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
</Link>
<PreBuildEvent>
<Command>
</Command>
<Message>
</Message>
</PreBuildEvent>
<CustomBuildStep>
<Command>
</Command>
</CustomBuildStep>
<CustomBuildStep>
<Message>
</Message>
<Outputs>
</Outputs>
</CustomBuildStep>
<PostBuildEvent>
<Command>
</Command>
</PostBuildEvent>
<PostBuildEvent>
<Message>
</Message>
</PostBuildEvent>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<ClCompile>
<PrecompiledHeader>NotUsing</PrecompiledHeader>
<WarningLevel>Level4</WarningLevel>
<Optimization>MaxSpeed</Optimization>
<FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>true</IntrinsicFunctions>
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>NDEBUG;FLUTTER_PLUGIN_IMPL;_WINDOWS;_USRDLL;_HAS_EXCEPTIONS=0;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
<ExceptionHandling>false</ExceptionHandling>
<TreatWarningAsError>true</TreatWarningAsError>
<DisableSpecificWarnings>4100</DisableSpecificWarnings>
<AdditionalIncludeDirectories>$(FLUTTER_EPHEMERAL_DIR);$(FLUTTER_EPHEMERAL_DIR)\cpp_client_wrapper\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
</ClCompile>
<Link>
<SubSystem>Windows</SubSystem>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<OptimizeReferences>true</OptimizeReferences>
<GenerateDebugInformation>true</GenerateDebugInformation>
<AdditionalDependencies>flutter_windows.dll.lib;%(AdditionalDependencies)</AdditionalDependencies>
<AdditionalLibraryDirectories>$(FLUTTER_EPHEMERAL_DIR);%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
</Link>
<PreBuildEvent>
<Command>
</Command>
<Message>
</Message>
</PreBuildEvent>
<CustomBuildStep>
<Command>
</Command>
</CustomBuildStep>
<CustomBuildStep>
<Message>
</Message>
<Outputs>
</Outputs>
</CustomBuildStep>
<PostBuildEvent>
<Command>
</Command>
</PostBuildEvent>
<PostBuildEvent>
<Message>
</Message>
</PostBuildEvent>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Profile|x64'">
<ClCompile>
<PrecompiledHeader>NotUsing</PrecompiledHeader>
<WarningLevel>Level4</WarningLevel>
<Optimization>MaxSpeed</Optimization>
<FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>true</IntrinsicFunctions>
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>NDEBUG;FLUTTER_PLUGIN_IMPL;_WINDOWS;_USRDLL;_HAS_EXCEPTIONS=0;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
<ExceptionHandling>false</ExceptionHandling>
<TreatWarningAsError>true</TreatWarningAsError>
<DisableSpecificWarnings>4100</DisableSpecificWarnings>
<AdditionalIncludeDirectories>$(FLUTTER_EPHEMERAL_DIR);$(FLUTTER_EPHEMERAL_DIR)\cpp_client_wrapper\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
</ClCompile>
<Link>
<SubSystem>Windows</SubSystem>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<OptimizeReferences>true</OptimizeReferences>
<GenerateDebugInformation>true</GenerateDebugInformation>
<AdditionalDependencies>flutter_windows.dll.lib;%(AdditionalDependencies)</AdditionalDependencies>
<AdditionalLibraryDirectories>$(FLUTTER_EPHEMERAL_DIR);%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
</Link>
<PreBuildEvent>
<Command>
</Command>
<Message>
</Message>
</PreBuildEvent>
<CustomBuildStep>
<Command>
</Command>
</CustomBuildStep>
<CustomBuildStep>
<Message>
</Message>
<Outputs>
</Outputs>
</CustomBuildStep>
<PostBuildEvent>
<Command>
</Command>
</PostBuildEvent>
<PostBuildEvent>
<Message>
</Message>
</PostBuildEvent>
</ItemDefinitionGroup>
<ItemGroup>
<ClInclude Include="$(FlutterPluginName)_plugin.h" />
<ClInclude Include="stdafx.h" />
<ClInclude Include="targetver.h" />
</ItemGroup>
<ItemGroup>
<ClCompile Include="$(FlutterPluginName)_plugin.cpp" />
<ClCompile Include="$(FLUTTER_EPHEMERAL_DIR)\cpp_client_wrapper\engine_method_result.cc" />
<ClCompile Include="$(FLUTTER_EPHEMERAL_DIR)\cpp_client_wrapper\plugin_registrar.cc" />
<ClCompile Include="$(FLUTTER_EPHEMERAL_DIR)\cpp_client_wrapper\standard_codec.cc" />
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
</ImportGroup>
</Project>
#include "{{projectName}}_plugin.h" #include "include/{{projectName}}/{{projectName}}_plugin.h"
// This must be included before many other Windows headers. // This must be included before many other Windows headers.
#include <windows.h> #include <windows.h>
......
...@@ -122,9 +122,10 @@ ...@@ -122,9 +122,10 @@
"templates/app/web/index.html.tmpl", "templates/app/web/index.html.tmpl",
"templates/app/web/manifest.json.tmpl", "templates/app/web/manifest.json.tmpl",
"templates/app/windows.tmpl/.gitignore", "templates/app/windows.tmpl/.gitignore",
"templates/app/windows.tmpl/AppConfiguration.props.tmpl", "templates/app/windows.tmpl/CMakeLists.txt.tmpl",
"templates/app/windows.tmpl/flutter/.template_version", "templates/app/windows.tmpl/flutter/.template_version",
"templates/app/windows.tmpl/FlutterBuild.vcxproj", "templates/app/windows.tmpl/flutter/CMakeLists.txt",
"templates/app/windows.tmpl/runner/CMakeLists.txt",
"templates/app/windows.tmpl/runner/flutter_window.cpp", "templates/app/windows.tmpl/runner/flutter_window.cpp",
"templates/app/windows.tmpl/runner/flutter_window.h", "templates/app/windows.tmpl/runner/flutter_window.h",
"templates/app/windows.tmpl/runner/main.cpp", "templates/app/windows.tmpl/runner/main.cpp",
...@@ -140,11 +141,6 @@ ...@@ -140,11 +141,6 @@
"templates/app/windows.tmpl/runner/win32_window.h", "templates/app/windows.tmpl/runner/win32_window.h",
"templates/app/windows.tmpl/runner/window_configuration.cpp.tmpl", "templates/app/windows.tmpl/runner/window_configuration.cpp.tmpl",
"templates/app/windows.tmpl/runner/window_configuration.h", "templates/app/windows.tmpl/runner/window_configuration.h",
"templates/app/windows.tmpl/Runner.sln",
"templates/app/windows.tmpl/Runner.vcxproj.filters",
"templates/app/windows.tmpl/Runner.vcxproj.tmpl",
"templates/app/windows.tmpl/scripts/bundle_assets_and_deps.bat",
"templates/app/windows.tmpl/scripts/prepare_dependencies.bat",
"templates/cocoapods/Podfile-ios-objc", "templates/cocoapods/Podfile-ios-objc",
"templates/cocoapods/Podfile-ios-swift", "templates/cocoapods/Podfile-ios-swift",
"templates/cocoapods/Podfile-macos", "templates/cocoapods/Podfile-macos",
...@@ -281,11 +277,9 @@ ...@@ -281,11 +277,9 @@
"templates/plugin/README.md.tmpl", "templates/plugin/README.md.tmpl",
"templates/plugin/test/projectName_test.dart.tmpl", "templates/plugin/test/projectName_test.dart.tmpl",
"templates/plugin/windows.tmpl/.gitignore", "templates/plugin/windows.tmpl/.gitignore",
"templates/plugin/windows.tmpl/plugin.vcxproj.filters", "templates/plugin/windows.tmpl/CMakeLists.txt.tmpl",
"templates/plugin/windows.tmpl/plugin.vcxproj.tmpl", "templates/plugin/windows.tmpl/include/projectName.tmpl/projectName_plugin.h.tmpl",
"templates/plugin/windows.tmpl/PluginInfo.props.tmpl",
"templates/plugin/windows.tmpl/projectName_plugin.cpp.tmpl", "templates/plugin/windows.tmpl/projectName_plugin.cpp.tmpl",
"templates/plugin/windows.tmpl/projectName_plugin.h.tmpl",
"templates/plugin/lib/projectName_web.dart.tmpl" "templates/plugin/lib/projectName_web.dart.tmpl"
] ]
} }
\ No newline at end of file
...@@ -9,10 +9,10 @@ import 'package:flutter_tools/src/base/file_system.dart'; ...@@ -9,10 +9,10 @@ import 'package:flutter_tools/src/base/file_system.dart';
import 'package:flutter_tools/src/base/platform.dart'; import 'package:flutter_tools/src/base/platform.dart';
import 'package:flutter_tools/src/base/utils.dart'; import 'package:flutter_tools/src/base/utils.dart';
import 'package:flutter_tools/src/cache.dart'; import 'package:flutter_tools/src/cache.dart';
import 'package:flutter_tools/src/cmake.dart';
import 'package:flutter_tools/src/commands/build.dart'; import 'package:flutter_tools/src/commands/build.dart';
import 'package:flutter_tools/src/commands/build_linux.dart'; import 'package:flutter_tools/src/commands/build_linux.dart';
import 'package:flutter_tools/src/features.dart'; import 'package:flutter_tools/src/features.dart';
import 'package:flutter_tools/src/linux/cmake.dart';
import 'package:flutter_tools/src/project.dart'; import 'package:flutter_tools/src/project.dart';
import 'package:process/process.dart'; import 'package:process/process.dart';
...@@ -267,7 +267,7 @@ void main() { ...@@ -267,7 +267,7 @@ void main() {
FeatureFlags: () => TestFeatureFlags(isLinuxEnabled: true), FeatureFlags: () => TestFeatureFlags(isLinuxEnabled: true),
}); });
testUsingContext('Linux build configures Makefile exports', () async { testUsingContext('Linux build configures CMake exports', () async {
final BuildCommand command = BuildCommand(); final BuildCommand command = BuildCommand();
setUpMockProjectFilesForBuild(); setUpMockProjectFilesForBuild();
processManager = FakeProcessManager.list(<FakeCommand>[ processManager = FakeProcessManager.list(<FakeCommand>[
...@@ -305,8 +305,8 @@ void main() { ...@@ -305,8 +305,8 @@ void main() {
final List<String> configLines = cmakeConfig.readAsLinesSync(); final List<String> configLines = cmakeConfig.readAsLinesSync();
expect(configLines, containsAll(<String>[ expect(configLines, containsAll(<String>[
'set(FLUTTER_ROOT "$_kTestFlutterRoot")', 'file(TO_CMAKE_PATH "$_kTestFlutterRoot" FLUTTER_ROOT)',
'set(PROJECT_DIR "${fileSystem.currentDirectory.path}")', 'file(TO_CMAKE_PATH "${fileSystem.currentDirectory.path}" PROJECT_DIR)',
' "DART_DEFINES=\\"foo.bar%3D2,fizz.far%3D3\\""', ' "DART_DEFINES=\\"foo.bar%3D2,fizz.far%3D3\\""',
' "DART_OBFUSCATION=\\"true\\""', ' "DART_OBFUSCATION=\\"true\\""',
' "EXTRA_FRONT_END_OPTIONS=\\"--enable-experiment%3Dnon-nullable\\""', ' "EXTRA_FRONT_END_OPTIONS=\\"--enable-experiment%3Dnon-nullable\\""',
...@@ -314,8 +314,8 @@ void main() { ...@@ -314,8 +314,8 @@ void main() {
' "SPLIT_DEBUG_INFO=\\"foo/\\""', ' "SPLIT_DEBUG_INFO=\\"foo/\\""',
' "TRACK_WIDGET_CREATION=\\"true\\""', ' "TRACK_WIDGET_CREATION=\\"true\\""',
' "TREE_SHAKE_ICONS=\\"true\\""', ' "TREE_SHAKE_ICONS=\\"true\\""',
' "FLUTTER_ROOT=\\"\${FLUTTER_ROOT}\\""', ' "FLUTTER_ROOT=\\"$_kTestFlutterRoot\\""',
' "PROJECT_DIR=\\"\${PROJECT_DIR}\\""', ' "PROJECT_DIR=\\"${fileSystem.currentDirectory.path}\\""',
' "FLUTTER_TARGET=\\"lib/other.dart\\""', ' "FLUTTER_TARGET=\\"lib/other.dart\\""',
' "BUNDLE_SKSL_PATH=\\"foo/bar.sksl.json\\""', ' "BUNDLE_SKSL_PATH=\\"foo/bar.sksl.json\\""',
])); ]));
......
...@@ -9,12 +9,10 @@ import 'package:flutter_tools/src/base/io.dart'; ...@@ -9,12 +9,10 @@ import 'package:flutter_tools/src/base/io.dart';
import 'package:flutter_tools/src/base/platform.dart'; import 'package:flutter_tools/src/base/platform.dart';
import 'package:flutter_tools/src/cache.dart'; import 'package:flutter_tools/src/cache.dart';
import 'package:flutter_tools/src/commands/build_windows.dart'; import 'package:flutter_tools/src/commands/build_windows.dart';
import 'package:flutter_tools/src/convert.dart';
import 'package:flutter_tools/src/features.dart'; import 'package:flutter_tools/src/features.dart';
import 'package:flutter_tools/src/windows/visual_studio.dart'; import 'package:flutter_tools/src/windows/visual_studio.dart';
import 'package:mockito/mockito.dart'; import 'package:mockito/mockito.dart';
import 'package:process/process.dart'; import 'package:process/process.dart';
import 'package:xml/xml.dart' as xml;
import '../../src/common.dart'; import '../../src/common.dart';
import '../../src/context.dart'; import '../../src/context.dart';
...@@ -22,9 +20,9 @@ import '../../src/mocks.dart'; ...@@ -22,9 +20,9 @@ import '../../src/mocks.dart';
import '../../src/testbed.dart'; import '../../src/testbed.dart';
const String flutterRoot = r'C:\flutter'; const String flutterRoot = r'C:\flutter';
const String solutionPath = r'C:\windows\Runner.sln'; const String buildFilePath = r'C:\windows\CMakeLists.txt';
const String visualStudioPath = r'C:\Program Files (x86)\Microsoft Visual Studio\2017\Community'; const String visualStudioPath = r'C:\Program Files (x86)\Microsoft Visual Studio\2017\Community';
const String vcvarsPath = visualStudioPath + r'\VC\Auxiliary\Build\vcvars64.bat'; const String cmakePath = visualStudioPath + r'\Common7\IDE\CommonExtensions\Microsoft\CMake\CMake\bin\cmake.exe';
final Platform windowsPlatform = FakePlatform( final Platform windowsPlatform = FakePlatform(
operatingSystem: 'windows', operatingSystem: 'windows',
...@@ -43,8 +41,7 @@ final Platform notWindowsPlatform = FakePlatform( ...@@ -43,8 +41,7 @@ final Platform notWindowsPlatform = FakePlatform(
void main() { void main() {
FileSystem fileSystem; FileSystem fileSystem;
MockProcessManager mockProcessManager; ProcessManager processManager;
MockProcess mockProcess;
MockVisualStudio mockVisualStudio; MockVisualStudio mockVisualStudio;
setUpAll(() { setUpAll(() {
...@@ -54,18 +51,7 @@ void main() { ...@@ -54,18 +51,7 @@ void main() {
setUp(() { setUp(() {
fileSystem = MemoryFileSystem.test(style: FileSystemStyle.windows); fileSystem = MemoryFileSystem.test(style: FileSystemStyle.windows);
Cache.flutterRoot = flutterRoot; Cache.flutterRoot = flutterRoot;
mockProcessManager = MockProcessManager();
mockProcess = MockProcess();
mockVisualStudio = MockVisualStudio(); mockVisualStudio = MockVisualStudio();
when(mockProcess.exitCode).thenAnswer((Invocation invocation) async {
return 0;
});
when(mockProcess.stderr).thenAnswer((Invocation invocation) {
return const Stream<List<int>>.empty();
});
when(mockProcess.stdout).thenAnswer((Invocation invocation) {
return Stream<List<int>>.fromIterable(<List<int>>[utf8.encode('STDOUT STUFF')]);
});
}); });
// Creates the mock files necessary to look like a Flutter project. // Creates the mock files necessary to look like a Flutter project.
...@@ -77,7 +63,7 @@ void main() { ...@@ -77,7 +63,7 @@ void main() {
// Creates the mock files necessary to run a build. // Creates the mock files necessary to run a build.
void setUpMockProjectFilesForBuild({int templateVersion}) { void setUpMockProjectFilesForBuild({int templateVersion}) {
fileSystem.file(solutionPath).createSync(recursive: true); fileSystem.file(buildFilePath).createSync(recursive: true);
setUpMockCoreProjectFiles(); setUpMockCoreProjectFiles();
final String versionFileSubpath = fileSystem.path.join('flutter', '.template_version'); final String versionFileSubpath = fileSystem.path.join('flutter', '.template_version');
...@@ -101,6 +87,50 @@ void main() { ...@@ -101,6 +87,50 @@ void main() {
projectTemplateVersionFile.writeAsStringSync(templateVersion.toString()); projectTemplateVersionFile.writeAsStringSync(templateVersion.toString());
} }
// Returns the command matching the build_windows call to generate CMake
// files.
FakeCommand cmakeGenerationCommand({void Function() onRun}) {
return FakeCommand(
command: <String>[
cmakePath,
'-S',
fileSystem.path.dirname(buildFilePath),
'-B',
r'build\windows',
'-G',
'Visual Studio 16 2019',
],
onRun: onRun,
);
}
// Returns the command matching the build_windows call to build.
FakeCommand buildCommand(String buildMode, {
bool verbose = false,
void Function() onRun,
String stdout = '',
}) {
return FakeCommand(
command: <String>[
cmakePath,
'--build',
r'build\windows',
'--config',
buildMode,
'--target',
'INSTALL',
if (verbose)
'--verbose'
],
environment: <String, String>{
if (verbose)
'VERBOSE_SCRIPT_LOGGING': 'true'
},
onRun: onRun,
stdout: stdout,
);
}
testUsingContext('Windows build fails when there is no vcvars64.bat', () async { testUsingContext('Windows build fails when there is no vcvars64.bat', () async {
final BuildWindowsCommand command = BuildWindowsCommand() final BuildWindowsCommand command = BuildWindowsCommand()
..visualStudioOverride = mockVisualStudio; ..visualStudioOverride = mockVisualStudio;
...@@ -122,7 +152,7 @@ void main() { ...@@ -122,7 +152,7 @@ void main() {
..visualStudioOverride = mockVisualStudio; ..visualStudioOverride = mockVisualStudio;
applyMocksToCommand(command); applyMocksToCommand(command);
setUpMockCoreProjectFiles(); setUpMockCoreProjectFiles();
when(mockVisualStudio.vcvarsPath).thenReturn(vcvarsPath); when(mockVisualStudio.cmakePath).thenReturn(cmakePath);
expect(createTestCommandRunner(command).run( expect(createTestCommandRunner(command).run(
const <String>['windows', '--no-pub'] const <String>['windows', '--no-pub']
...@@ -139,7 +169,7 @@ void main() { ...@@ -139,7 +169,7 @@ void main() {
..visualStudioOverride = mockVisualStudio; ..visualStudioOverride = mockVisualStudio;
applyMocksToCommand(command); applyMocksToCommand(command);
setUpMockProjectFilesForBuild(); setUpMockProjectFilesForBuild();
when(mockVisualStudio.vcvarsPath).thenReturn(vcvarsPath); when(mockVisualStudio.cmakePath).thenReturn(cmakePath);
expect(createTestCommandRunner(command).run( expect(createTestCommandRunner(command).run(
const <String>['windows', '--no-pub'] const <String>['windows', '--no-pub']
...@@ -188,19 +218,14 @@ void main() { ...@@ -188,19 +218,14 @@ void main() {
..visualStudioOverride = mockVisualStudio; ..visualStudioOverride = mockVisualStudio;
applyMocksToCommand(command); applyMocksToCommand(command);
setUpMockProjectFilesForBuild(); setUpMockProjectFilesForBuild();
when(mockVisualStudio.vcvarsPath).thenReturn(vcvarsPath); when(mockVisualStudio.cmakePath).thenReturn(cmakePath);
when(mockProcessManager.start(<String>[ processManager = FakeProcessManager.list(<FakeCommand>[
fileSystem.path.join(flutterRoot, 'packages', 'flutter_tools', 'bin', 'vs_build.bat'), cmakeGenerationCommand(),
vcvarsPath, buildCommand('Release',
fileSystem.path.basename(solutionPath), stdout: 'STDOUT STUFF',
'Release', ),
], ]);
environment: <String, String>{},
workingDirectory: fileSystem.path.dirname(solutionPath))
).thenAnswer((Invocation invocation) async {
return mockProcess;
});
await createTestCommandRunner(command).run( await createTestCommandRunner(command).run(
const <String>['windows', '--no-pub'] const <String>['windows', '--no-pub']
...@@ -209,7 +234,7 @@ void main() { ...@@ -209,7 +234,7 @@ void main() {
expect(testLogger.traceText, contains('STDOUT STUFF')); expect(testLogger.traceText, contains('STDOUT STUFF'));
}, overrides: <Type, Generator>{ }, overrides: <Type, Generator>{
FileSystem: () => fileSystem, FileSystem: () => fileSystem,
ProcessManager: () => mockProcessManager, ProcessManager: () => processManager,
Platform: () => windowsPlatform, Platform: () => windowsPlatform,
FeatureFlags: () => TestFeatureFlags(isWindowsEnabled: true), FeatureFlags: () => TestFeatureFlags(isWindowsEnabled: true),
}); });
...@@ -219,21 +244,15 @@ void main() { ...@@ -219,21 +244,15 @@ void main() {
..visualStudioOverride = mockVisualStudio; ..visualStudioOverride = mockVisualStudio;
applyMocksToCommand(command); applyMocksToCommand(command);
setUpMockProjectFilesForBuild(); setUpMockProjectFilesForBuild();
when(mockVisualStudio.vcvarsPath).thenReturn(vcvarsPath); when(mockVisualStudio.cmakePath).thenReturn(cmakePath);
when(mockProcessManager.start(<String>[ processManager = FakeProcessManager.list(<FakeCommand>[
fileSystem.path.join(flutterRoot, 'packages', 'flutter_tools', 'bin', 'vs_build.bat'), cmakeGenerationCommand(),
vcvarsPath, buildCommand('Release',
fileSystem.path.basename(solutionPath), verbose: true,
'Release', stdout: 'STDOUT STUFF',
], ),
environment: <String, String>{ ]);
'VERBOSE_SCRIPT_LOGGING': 'true',
},
workingDirectory: fileSystem.path.dirname(solutionPath))
).thenAnswer((Invocation invocation) async {
return mockProcess;
});
await createTestCommandRunner(command).run( await createTestCommandRunner(command).run(
const <String>['windows', '--no-pub', '-v'] const <String>['windows', '--no-pub', '-v']
...@@ -242,29 +261,24 @@ void main() { ...@@ -242,29 +261,24 @@ void main() {
expect(testLogger.traceText, isNot(contains('STDOUT STUFF'))); expect(testLogger.traceText, isNot(contains('STDOUT STUFF')));
}, overrides: <Type, Generator>{ }, overrides: <Type, Generator>{
FileSystem: () => fileSystem, FileSystem: () => fileSystem,
ProcessManager: () => mockProcessManager, ProcessManager: () => processManager,
Platform: () => windowsPlatform, Platform: () => windowsPlatform,
FeatureFlags: () => TestFeatureFlags(isWindowsEnabled: true), FeatureFlags: () => TestFeatureFlags(isWindowsEnabled: true),
}); });
testUsingContext('Windows build invokes msbuild and writes generated files', () async { testUsingContext('Windows build invokes build and writes generated files', () async {
final BuildWindowsCommand command = BuildWindowsCommand() final BuildWindowsCommand command = BuildWindowsCommand()
..visualStudioOverride = mockVisualStudio; ..visualStudioOverride = mockVisualStudio;
applyMocksToCommand(command); applyMocksToCommand(command);
setUpMockProjectFilesForBuild(); setUpMockProjectFilesForBuild();
when(mockVisualStudio.vcvarsPath).thenReturn(vcvarsPath); when(mockVisualStudio.cmakePath).thenReturn(cmakePath);
when(mockProcessManager.start(<String>[ processManager = FakeProcessManager.list(<FakeCommand>[
fileSystem.path.join(flutterRoot, 'packages', 'flutter_tools', 'bin', 'vs_build.bat'), cmakeGenerationCommand(),
vcvarsPath, buildCommand('Release'),
fileSystem.path.basename(solutionPath), ]);
'Release', fileSystem.file(fileSystem.path.join('lib', 'other.dart'))
], .createSync(recursive: true);
environment: <String, String>{},
workingDirectory: fileSystem.path.dirname(solutionPath))
).thenAnswer((Invocation invocation) async {
return mockProcess;
});
await createTestCommandRunner(command).run( await createTestCommandRunner(command).run(
const <String>[ const <String>[
...@@ -278,30 +292,40 @@ void main() { ...@@ -278,30 +292,40 @@ void main() {
'--dart-define=foo=a', '--dart-define=foo=a',
'--dart-define=bar=b', '--dart-define=bar=b',
r'--bundle-sksl-path=foo\bar.sksl.json', r'--bundle-sksl-path=foo\bar.sksl.json',
r'--target=lib\main.dart', r'--target=lib\other.dart',
] ]
); );
// Spot-check important elements from the properties file. final File cmakeConfig = fileSystem.currentDirectory
final File propsFile = fileSystem.file(r'C:\windows\flutter\ephemeral\Generated.props'); .childDirectory('windows')
expect(propsFile, exists); .childDirectory('flutter')
.childDirectory('ephemeral')
final xml.XmlDocument props = xml.parse(propsFile.readAsStringSync()); .childFile('generated_config.cmake');
expect(props.findAllElements('PropertyGroup').first.getAttribute('Label'), 'UserMacros');
expect(props.findAllElements('ItemGroup').length, 1); expect(cmakeConfig, exists);
expect(props.findAllElements('FLUTTER_ROOT').first.text, flutterRoot);
expect(props.findAllElements('TRACK_WIDGET_CREATION').first.text, 'true'); final List<String> configLines = cmakeConfig.readAsLinesSync();
expect(props.findAllElements('TREE_SHAKE_ICONS').first.text, 'true');
expect(props.findAllElements('EXTRA_GEN_SNAPSHOT_OPTIONS').first.text, '--enable-experiment%3Dnon-nullable'); // Backslashes are escaped in the file, which is why this uses both raw
expect(props.findAllElements('EXTRA_FRONT_END_OPTIONS').first.text, '--enable-experiment%3Dnon-nullable'); // strings and double backslashes.
expect(props.findAllElements('DART_DEFINES').first.text, 'foo%3Da,bar%3Db'); expect(configLines, containsAll(<String>[
expect(props.findAllElements('DART_OBFUSCATION').first.text, 'true'); r'file(TO_CMAKE_PATH "C:\\flutter" FLUTTER_ROOT)',
expect(props.findAllElements('SPLIT_DEBUG_INFO').first.text, r'C:\foo\'); r'file(TO_CMAKE_PATH "C:\\" PROJECT_DIR)',
expect(props.findAllElements('FLUTTER_TARGET').first.text, r'lib\main.dart'); r' "DART_DEFINES=\"foo%3Da,bar%3Db\""',
expect(props.findAllElements('BUNDLE_SKSL_PATH').first.text, r'foo\bar.sksl.json'); r' "DART_OBFUSCATION=\"true\""',
r' "EXTRA_FRONT_END_OPTIONS=\"--enable-experiment%3Dnon-nullable\""',
r' "EXTRA_GEN_SNAPSHOT_OPTIONS=\"--enable-experiment%3Dnon-nullable\""',
r' "SPLIT_DEBUG_INFO=\"C:\\foo\\\""',
r' "TRACK_WIDGET_CREATION=\"true\""',
r' "TREE_SHAKE_ICONS=\"true\""',
r' "FLUTTER_ROOT=\"C:\\flutter\""',
r' "PROJECT_DIR=\"C:\\\""',
r' "FLUTTER_TARGET=\"lib\\other.dart\""',
r' "BUNDLE_SKSL_PATH=\"foo\\bar.sksl.json\""',
]));
}, overrides: <Type, Generator>{ }, overrides: <Type, Generator>{
FileSystem: () => fileSystem, FileSystem: () => fileSystem,
ProcessManager: () => mockProcessManager, ProcessManager: () => processManager,
Platform: () => windowsPlatform, Platform: () => windowsPlatform,
FeatureFlags: () => TestFeatureFlags(isWindowsEnabled: true), FeatureFlags: () => TestFeatureFlags(isWindowsEnabled: true),
}); });
...@@ -311,26 +335,19 @@ void main() { ...@@ -311,26 +335,19 @@ void main() {
..visualStudioOverride = mockVisualStudio; ..visualStudioOverride = mockVisualStudio;
applyMocksToCommand(command); applyMocksToCommand(command);
setUpMockProjectFilesForBuild(); setUpMockProjectFilesForBuild();
when(mockVisualStudio.vcvarsPath).thenReturn(vcvarsPath); when(mockVisualStudio.cmakePath).thenReturn(cmakePath);
when(mockProcessManager.start(<String>[ processManager = FakeProcessManager.list(<FakeCommand>[
fileSystem.path.join(flutterRoot, 'packages', 'flutter_tools', 'bin', 'vs_build.bat'), cmakeGenerationCommand(),
vcvarsPath, buildCommand('Profile'),
fileSystem.path.basename(solutionPath), ]);
'Profile',
],
environment: <String, String>{},
workingDirectory: fileSystem.path.dirname(solutionPath))
).thenAnswer((Invocation invocation) async {
return mockProcess;
});
await createTestCommandRunner(command).run( await createTestCommandRunner(command).run(
const <String>['windows', '--profile', '--no-pub'] const <String>['windows', '--profile', '--no-pub']
); );
}, overrides: <Type, Generator>{ }, overrides: <Type, Generator>{
FileSystem: () => fileSystem, FileSystem: () => fileSystem,
ProcessManager: () => mockProcessManager, ProcessManager: () => processManager,
Platform: () => windowsPlatform, Platform: () => windowsPlatform,
FeatureFlags: () => TestFeatureFlags(isWindowsEnabled: true), FeatureFlags: () => TestFeatureFlags(isWindowsEnabled: true),
}); });
......
...@@ -777,7 +777,7 @@ void main() { ...@@ -777,7 +777,7 @@ void main() {
await runner.run(<String>['create', '--no-pub', projectDir.path]); await runner.run(<String>['create', '--no-pub', projectDir.path]);
expect(projectDir.childDirectory('windows').childFile('Runner.sln').existsSync(), true); expect(projectDir.childDirectory('windows').childFile('CMakeLists.txt').existsSync(), true);
}, overrides: <Type, Generator>{ }, overrides: <Type, Generator>{
FeatureFlags: () => TestFeatureFlags(isWindowsEnabled: true), FeatureFlags: () => TestFeatureFlags(isWindowsEnabled: true),
}); });
...@@ -792,7 +792,7 @@ void main() { ...@@ -792,7 +792,7 @@ void main() {
await runner.run(<String>['create', '--no-pub', projectDir.path]); await runner.run(<String>['create', '--no-pub', projectDir.path]);
expect(projectDir.childDirectory('windows').childFile('Runner.sln').existsSync(), false); expect(projectDir.childDirectory('windows').childFile('CMakeLists.txt').existsSync(), false);
}, overrides: <Type, Generator>{ }, overrides: <Type, Generator>{
FeatureFlags: () => TestFeatureFlags(isWindowsEnabled: false), FeatureFlags: () => TestFeatureFlags(isWindowsEnabled: false),
}); });
...@@ -807,7 +807,7 @@ void main() { ...@@ -807,7 +807,7 @@ void main() {
await runner.run(<String>['create', '--no-pub', '--template=plugin', '--platforms=windows', projectDir.path]); await runner.run(<String>['create', '--no-pub', '--template=plugin', '--platforms=windows', projectDir.path]);
expect(projectDir.childDirectory('windows').childFile('plugin.vcxproj').existsSync(), true); expect(projectDir.childDirectory('windows').childFile('CMakeLists.txt').existsSync(), true);
expect(projectDir.childDirectory('example').childDirectory('windows').existsSync(), true); expect(projectDir.childDirectory('example').childDirectory('windows').existsSync(), true);
validatePubspecForPlugin(projectDir: projectDir.absolute.path, expectedPlatforms: const <String>[ validatePubspecForPlugin(projectDir: projectDir.absolute.path, expectedPlatforms: const <String>[
'windows' 'windows'
...@@ -827,7 +827,7 @@ void main() { ...@@ -827,7 +827,7 @@ void main() {
await runner.run(<String>['create', '--no-pub', '--template=plugin', projectDir.path]); await runner.run(<String>['create', '--no-pub', '--template=plugin', projectDir.path]);
expect(projectDir.childDirectory('windows').childFile('plugin.vcxproj').existsSync(), false); expect(projectDir.childDirectory('windows').childFile('CMakeLists.txt').existsSync(), false);
expect(projectDir.childDirectory('example').childDirectory('windows').existsSync(), false); expect(projectDir.childDirectory('example').childDirectory('windows').existsSync(), false);
}, overrides: <Type, Generator>{ }, overrides: <Type, Generator>{
FeatureFlags: () => TestFeatureFlags(isWindowsEnabled: false), FeatureFlags: () => TestFeatureFlags(isWindowsEnabled: false),
......
...@@ -80,10 +80,9 @@ void main() { ...@@ -80,10 +80,9 @@ void main() {
when(windowsProject.pluginConfigKey).thenReturn('windows'); when(windowsProject.pluginConfigKey).thenReturn('windows');
final Directory windowsManagedDirectory = flutterProject.directory.childDirectory('windows').childDirectory('flutter'); final Directory windowsManagedDirectory = flutterProject.directory.childDirectory('windows').childDirectory('flutter');
when(windowsProject.managedDirectory).thenReturn(windowsManagedDirectory); when(windowsProject.managedDirectory).thenReturn(windowsManagedDirectory);
when(windowsProject.vcprojFile).thenReturn(windowsManagedDirectory.parent.childFile('Runner.vcxproj')); when(windowsProject.cmakeFile).thenReturn(windowsManagedDirectory.parent.childFile('CMakeLists.txt'));
when(windowsProject.solutionFile).thenReturn(windowsManagedDirectory.parent.childFile('Runner.sln')); when(windowsProject.generatedPluginCmakeFile).thenReturn(windowsManagedDirectory.childFile('generated_plugins.mk'));
when(windowsProject.pluginSymlinkDirectory).thenReturn(windowsManagedDirectory.childDirectory('ephemeral').childDirectory('.plugin_symlinks')); when(windowsProject.pluginSymlinkDirectory).thenReturn(windowsManagedDirectory.childDirectory('ephemeral').childDirectory('.plugin_symlinks'));
when(windowsProject.generatedPluginPropertySheetFile).thenReturn(windowsManagedDirectory.childFile('GeneratedPlugins.props'));
when(windowsProject.existsSync()).thenReturn(false); when(windowsProject.existsSync()).thenReturn(false);
linuxProject = MockLinuxProject(); linuxProject = MockLinuxProject();
when(flutterProject.linux).thenReturn(linuxProject); when(flutterProject.linux).thenReturn(linuxProject);
...@@ -323,44 +322,6 @@ dependencies: ...@@ -323,44 +322,6 @@ dependencies:
project.podManifestLock.createSync(recursive: true); project.podManifestLock.createSync(recursive: true);
} }
// Creates a Windows solution file sufficient to allow plugin injection
// to run without failing.
void createDummyWindowsSolutionFile() {
windowsProject.solutionFile.createSync(recursive: true);
// This isn't a valid solution file, but it's just enough to work with the
// plugin injection.
windowsProject.solutionFile.writeAsStringSync('''
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Runner", "Runner.vcxproj", "{3842E94C-E348-463A-ADBE-625A2B69B628}"
ProjectSection(ProjectDependencies) = postProject
{6419BF13-6ECD-4CD2-9E85-E566A1F03F8F} = {6419BF13-6ECD-4CD2-9E85-E566A1F03F8F}
EndProjectSection
EndProject
Global
GlobalSection(ProjectConfigurationPlatforms) = postSolution
EndGlobalSection
EndGlobal''');
}
// Creates a Windows project file for dummyPackageDirectory sufficient to
// allow plugin injection to run without failing.
void createDummyPluginWindowsProjectFile() {
final File projectFile = dummyPackageDirectory
.parent
.childDirectory('windows')
.childFile('plugin.vcxproj');
projectFile.createSync(recursive: true);
// This isn't a valid project file, but it's just enough to work with the
// plugin injection.
projectFile.writeAsStringSync('''
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup Label="Globals">
<ProjectGuid>{5919689F-A5D5-462C-AF50-D405CCEF89B8}</ProjectGuid>'}
<ProjectName>apackage</ProjectName>
</PropertyGroup>
</Project>''');
}
group('refreshPlugins', () { group('refreshPlugins', () {
testUsingContext('Refreshing the plugin list is a no-op when the plugins list stays empty', () async { testUsingContext('Refreshing the plugin list is a no-op when the plugins list stays empty', () async {
await refreshPluginsList(flutterProject); await refreshPluginsList(flutterProject);
...@@ -1051,8 +1012,6 @@ flutter: ...@@ -1051,8 +1012,6 @@ flutter:
when(featureFlags.isWindowsEnabled).thenReturn(true); when(featureFlags.isWindowsEnabled).thenReturn(true);
when(flutterProject.isModule).thenReturn(false); when(flutterProject.isModule).thenReturn(false);
configureDummyPackageAsPlugin(); configureDummyPackageAsPlugin();
createDummyWindowsSolutionFile();
createDummyPluginWindowsProjectFile();
await injectPlugins(flutterProject, checkProjects: true); await injectPlugins(flutterProject, checkProjects: true);
...@@ -1083,9 +1042,6 @@ flutter: ...@@ -1083,9 +1042,6 @@ flutter:
dartPluginClass: SomePlugin dartPluginClass: SomePlugin
'''); ''');
createDummyWindowsSolutionFile();
createDummyPluginWindowsProjectFile();
await injectPlugins(flutterProject, checkProjects: true); await injectPlugins(flutterProject, checkProjects: true);
final File registrantImpl = windowsProject.managedDirectory.childFile('generated_plugin_registrant.cc'); final File registrantImpl = windowsProject.managedDirectory.childFile('generated_plugin_registrant.cc');
...@@ -1114,9 +1070,6 @@ flutter: ...@@ -1114,9 +1070,6 @@ flutter:
dartPluginClass: SomePlugin dartPluginClass: SomePlugin
'''); ''');
createDummyWindowsSolutionFile();
createDummyPluginWindowsProjectFile();
await injectPlugins(flutterProject, checkProjects: true); await injectPlugins(flutterProject, checkProjects: true);
final File registrantImpl = windowsProject.managedDirectory.childFile('generated_plugin_registrant.cc'); final File registrantImpl = windowsProject.managedDirectory.childFile('generated_plugin_registrant.cc');
...@@ -1130,39 +1083,22 @@ flutter: ...@@ -1130,39 +1083,22 @@ flutter:
FeatureFlags: () => featureFlags, FeatureFlags: () => featureFlags,
}); });
testUsingContext('Injecting creates generated Windows plugin properties', () async { testUsingContext('Injecting creates generated Windows plugin CMake file', () async {
when(windowsProject.existsSync()).thenReturn(true); when(windowsProject.existsSync()).thenReturn(true);
when(featureFlags.isWindowsEnabled).thenReturn(true); when(featureFlags.isWindowsEnabled).thenReturn(true);
when(flutterProject.isModule).thenReturn(false); when(flutterProject.isModule).thenReturn(false);
configureDummyPackageAsPlugin(); configureDummyPackageAsPlugin();
createDummyWindowsSolutionFile();
createDummyPluginWindowsProjectFile();
await injectPlugins(flutterProject, checkProjects: true); await injectPlugins(flutterProject, checkProjects: true);
final File properties = windowsProject.generatedPluginPropertySheetFile; final File pluginMakefile = windowsProject.generatedPluginCmakeFile;
final String includePath = fs.path.join('flutter', 'ephemeral', '.plugin_symlinks', 'apackage', 'windows');
expect(properties.existsSync(), isTrue); expect(pluginMakefile.existsSync(), isTrue);
expect(properties.readAsStringSync(), contains('apackage_plugin.lib')); final String contents = pluginMakefile.readAsStringSync();
expect(properties.readAsStringSync(), contains('>$includePath;')); expect(contents, contains('apackage'));
}, overrides: <Type, Generator>{ expect(contents, contains('target_link_libraries(\${BINARY_NAME} PRIVATE \${plugin}_plugin)'));
FileSystem: () => fs, expect(contents, contains('list(APPEND PLUGIN_BUNDLED_LIBRARIES \$<TARGET_FILE:\${plugin}_plugin>)'));
ProcessManager: () => FakeProcessManager.any(), expect(contents, contains('list(APPEND PLUGIN_BUNDLED_LIBRARIES \${\${plugin}_bundled_libraries})'));
FeatureFlags: () => featureFlags,
});
testUsingContext('Injecting updates Windows solution file', () async {
when(windowsProject.existsSync()).thenReturn(true);
when(featureFlags.isWindowsEnabled).thenReturn(true);
when(flutterProject.isModule).thenReturn(false);
configureDummyPackageAsPlugin();
createDummyWindowsSolutionFile();
createDummyPluginWindowsProjectFile();
await injectPlugins(flutterProject, checkProjects: true);
expect(windowsProject.solutionFile.readAsStringSync(), contains(r'apackage\windows\plugin.vcxproj'));
}, overrides: <Type, Generator>{ }, overrides: <Type, Generator>{
FileSystem: () => fs, FileSystem: () => fs,
ProcessManager: () => FakeProcessManager.any(), ProcessManager: () => FakeProcessManager.any(),
......
...@@ -223,7 +223,7 @@ void main() { ...@@ -223,7 +223,7 @@ void main() {
}); });
testUsingContext('injects plugins for Linux', () async { testUsingContext('injects plugins for Linux', () async {
final FlutterProject project = await someProject(); final FlutterProject project = await someProject();
project.linux.managedDirectory.createSync(recursive: true); project.linux.cmakeFile.createSync(recursive: true);
await project.ensureReadyForPlatformSpecificTooling(); await project.ensureReadyForPlatformSpecificTooling();
expectExists(project.linux.managedDirectory.childFile('generated_plugin_registrant.h')); expectExists(project.linux.managedDirectory.childFile('generated_plugin_registrant.h'));
expectExists(project.linux.managedDirectory.childFile('generated_plugin_registrant.cc')); expectExists(project.linux.managedDirectory.childFile('generated_plugin_registrant.cc'));
...@@ -238,21 +238,7 @@ void main() { ...@@ -238,21 +238,7 @@ void main() {
}); });
testUsingContext('injects plugins for Windows', () async { testUsingContext('injects plugins for Windows', () async {
final FlutterProject project = await someProject(); final FlutterProject project = await someProject();
project.windows.managedDirectory.createSync(recursive: true); project.windows.cmakeFile.createSync(recursive: true);
project.windows.solutionFile.createSync(recursive: true);
// Just enough solution file to allow injection to pass.
// TODO(stuartmorgan): Consider allowing injecting a mock solution util
// class into the test environment instead.
project.windows.solutionFile.writeAsStringSync('''
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Runner", "Runner.vcxproj", "{3842E94C-E348-463A-ADBE-625A2B69B628}"
ProjectSection(ProjectDependencies) = postProject
{6419BF13-6ECD-4CD2-9E85-E566A1F03F8F} = {6419BF13-6ECD-4CD2-9E85-E566A1F03F8F}
EndProjectSection
EndProject
Global
GlobalSection(ProjectConfigurationPlatforms) = postSolution
EndGlobalSection
EndGlobal''');
await project.ensureReadyForPlatformSpecificTooling(); await project.ensureReadyForPlatformSpecificTooling();
expectExists(project.windows.managedDirectory.childFile('generated_plugin_registrant.h')); expectExists(project.windows.managedDirectory.childFile('generated_plugin_registrant.h'));
expectExists(project.windows.managedDirectory.childFile('generated_plugin_registrant.cc')); expectExists(project.windows.managedDirectory.childFile('generated_plugin_registrant.cc'));
......
// 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:flutter_tools/src/windows/property_sheet.dart';
import '../../src/common.dart';
void main() {
group('Property Sheet', () {
test('Base file matches expected format', () async {
const String baseFile = '''
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ImportGroup Label="PropertySheets"/>
<PropertyGroup Label="UserMacros"/>
<PropertyGroup/>
<ItemDefinitionGroup/>
<ItemGroup/>
</Project>''';
const PropertySheet sheet = PropertySheet();
expect(sheet.toString(), baseFile);
});
test('Environment variable generate the correct elements', () async {
const Map<String, String> environment = <String, String>{'FOO': 'Bar'};
const PropertySheet sheet = PropertySheet(environmentVariables: environment);
final String propsContent = sheet.toString();
expect(propsContent, contains('<FOO>Bar</FOO>'));
expect(propsContent, contains('''
<BuildMacro Include="FOO">
<Value>\$(FOO)</Value>
<EnvironmentVariable>true</EnvironmentVariable>
</BuildMacro>'''));
});
test('Include paths generate the correct elements', () async {
const PropertySheet sheet = PropertySheet(includePaths: <String>['foo/bar', 'baz']);
final String propsContent = sheet.toString();
expect(propsContent, contains('<AdditionalIncludeDirectories>foo/bar;baz;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>'));
});
test('Library dependencies generate the correct elements', () async {
const PropertySheet sheet = PropertySheet(libraryDependencies: <String>['foo.lib', 'bar.lib']);
final String propsContent = sheet.toString();
expect(propsContent, contains('<AdditionalDependencies>foo.lib;bar.lib;%(AdditionalDependencies)</AdditionalDependencies>'));
});
});
}
// 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/memory.dart';
import 'package:file_testing/file_testing.dart';
import 'package:flutter_tools/src/base/file_system.dart';
import 'package:flutter_tools/src/windows/visual_studio_project.dart';
import '../../src/common.dart';
void main() {
group('Visual Studio Project', () {
String generateProjectContents({String guid, String name}) {
// A bare-bones project.
return '''
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup Label="ProjectConfigurations">
<ProjectConfiguration Include="Debug|x64">
<Configuration>Debug</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|x64">
<Configuration>Release</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
</ItemGroup>
<PropertyGroup Label="Globals">
<VCProjectVersion>15.0</VCProjectVersion>
${guid == null ? '' : '<ProjectGuid>{$guid}</ProjectGuid>'}
${name == null ? '' : '<ProjectName>$name</ProjectName>'}
<WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>
</PropertyGroup>
<Import Project="\$(VCTargetsPath)\\Microsoft.Cpp.Default.props" />
<PropertyGroup Label="Configuration">
<PlatformToolset>v142</PlatformToolset>
</PropertyGroup>
<Import Project="\$(VCTargetsPath)\\Microsoft.Cpp.props" />
<ImportGroup Label="ExtensionSettings">
</ImportGroup>
<ImportGroup Label="Shared">
</ImportGroup>
<ImportGroup Label="PropertySheets" />
<PropertyGroup Label="UserMacros" />
<PropertyGroup />
<ItemDefinitionGroup />
<ItemGroup>
</ItemGroup>
<Import Project="\$(VCTargetsPath)\\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
</ImportGroup>
</Project>''';
}
test('Property extraction works on a simple vcxproj', () async {
final FileSystem fileSystem = MemoryFileSystem();
const String guid = '017C4BAC-FEBA-406D-8A2C-3099FFE9D811';
const String name = 'Test';
final File projectFile = fileSystem.file('aproject.vcxproj');
projectFile.writeAsStringSync(generateProjectContents(guid: guid, name: name));
final VisualStudioProject project = VisualStudioProject(projectFile, fileSystem: fileSystem);
expect(project.formatUnderstood, true);
expect(project.guid, guid);
expect(project.name, name);
});
test('Missing GUID returns null', () async {
final FileSystem fileSystem = MemoryFileSystem();
final File projectFile = fileSystem.file('aproject.vcxproj');
projectFile.writeAsStringSync(generateProjectContents());
final VisualStudioProject project = VisualStudioProject(projectFile, fileSystem: fileSystem);
expect(project.formatUnderstood, true);
expect(project.guid, null);
});
test('Missing project name uses filename', () async {
final FileSystem fileSystem = MemoryFileSystem();
final File projectFile = fileSystem.file('aproject.vcxproj');
projectFile.writeAsStringSync(generateProjectContents());
final VisualStudioProject project = VisualStudioProject(projectFile, fileSystem: fileSystem);
expect(project.formatUnderstood, true);
expect(project.name, 'aproject');
});
test('Unknown file contents creates an object, and return false for formatUnderstood', () async {
final FileSystem fileSystem = MemoryFileSystem();
final File projectFile = fileSystem.file('aproject.vcxproj');
projectFile.writeAsStringSync('This is not XML!');
final VisualStudioProject project = VisualStudioProject(projectFile, fileSystem: fileSystem);
expect(project.formatUnderstood, false);
});
test('Missing project file throws on creation', () async {
final FileSystem fileSystem = MemoryFileSystem();
final File projectFile = fileSystem.file('aproject.vcxproj');
expect(() => VisualStudioProject(projectFile, fileSystem: fileSystem), throwsFileSystemException());
});
});
}
// 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/memory.dart';
import 'package:file_testing/file_testing.dart';
import 'package:flutter_tools/src/base/file_system.dart';
import 'package:flutter_tools/src/convert.dart';
import 'package:flutter_tools/src/plugins.dart';
import 'package:flutter_tools/src/platform_plugins.dart';
import 'package:flutter_tools/src/project.dart';
import 'package:flutter_tools/src/windows/visual_studio_solution_utils.dart';
import 'package:mockito/mockito.dart';
import '../../src/common.dart';
void main() {
group('Visual Studio Solution Utils', () {
// Magic values; see visual_studio_solution_utils.dart.
const String solutionTypeGuidFolder = '2150E333-8FDC-42A3-9474-1A3956D46DE8';
const String solutionTypeGuidVcxproj = '8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942';
const String flutterPluginSolutionFolderGuid = '5C2E738A-1DD3-445A-AAC8-EEB9648DD07C';
// Arbitrary random GUIDs to use for fake plugins.
const String pluginAGuid = '9F200BC4-A747-43D1-8B72-B778F2C4D048';
const String pluginBGuid = '39AC79B8-28A6-4526-B5FF-9C83F59B3AF0';
const String pluginCGuid = '8E010714-28FF-416A-BC6F-9CDE336A02A7';
const String pluginDGuid = '304F1860-7E8B-4C99-8E1D-F5E55259F5C3';
FileSystem fileSystem;
MockWindowsProject project;
setUp(() async {
fileSystem = MemoryFileSystem.test(style: FileSystemStyle.windows);
project = MockWindowsProject();
when(project.pluginConfigKey).thenReturn('windows');
final Directory windowsManagedDirectory = fileSystem.directory('C:').childDirectory('windows').childDirectory('flutter');
when(project.solutionFile).thenReturn(windowsManagedDirectory.parent.childFile('Runner.sln'));
when(project.vcprojFile).thenReturn(windowsManagedDirectory.parent.childFile('Runner.vcxproj'));
when(project.pluginSymlinkDirectory).thenReturn(windowsManagedDirectory.childDirectory('ephemeral').childDirectory('.plugin_symlinks'));
});
// Returns a solution file contents for a solution without any plugins.
void writeSolutionWithoutPlugins() {
project.solutionFile.createSync(recursive: true);
project.solutionFile.writeAsStringSync('''
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16
VisualStudioVersion = 16.0.29709.97
MinimumVisualStudioVersion = 10.0.40219.1
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Runner", "Runner.vcxproj", "{3842E94C-E348-463A-ADBE-625A2B69B628}"
ProjectSection(ProjectDependencies) = postProject
{6419BF13-6ECD-4CD2-9E85-E566A1F03F8F} = {6419BF13-6ECD-4CD2-9E85-E566A1F03F8F}
EndProjectSection
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Flutter Build", "FlutterBuild.vcxproj", "{6419BF13-6ECD-4CD2-9E85-E566A1F03F8F}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|x64 = Debug|x64
Profile|x64 = Profile|x64
Release|x64 = Release|x64
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{3842E94C-E348-463A-ADBE-625A2B69B628}.Debug|x64.ActiveCfg = Debug|x64
{3842E94C-E348-463A-ADBE-625A2B69B628}.Debug|x64.Build.0 = Debug|x64
{3842E94C-E348-463A-ADBE-625A2B69B628}.Profile|x64.ActiveCfg = Profile|x64
{3842E94C-E348-463A-ADBE-625A2B69B628}.Profile|x64.Build.0 = Profile|x64
{3842E94C-E348-463A-ADBE-625A2B69B628}.Release|x64.ActiveCfg = Release|x64
{3842E94C-E348-463A-ADBE-625A2B69B628}.Release|x64.Build.0 = Release|x64
{6419BF13-6ECD-4CD2-9E85-E566A1F03F8F}.Debug|x64.ActiveCfg = Debug|x64
{6419BF13-6ECD-4CD2-9E85-E566A1F03F8F}.Debug|x64.Build.0 = Debug|x64
{6419BF13-6ECD-4CD2-9E85-E566A1F03F8F}.Profile|x64.ActiveCfg = Profile|x64
{6419BF13-6ECD-4CD2-9E85-E566A1F03F8F}.Profile|x64.Build.0 = Profile|x64
{6419BF13-6ECD-4CD2-9E85-E566A1F03F8F}.Release|x64.ActiveCfg = Release|x64
{6419BF13-6ECD-4CD2-9E85-E566A1F03F8F}.Release|x64.Build.0 = Release|x64
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {B8A69CB0-A974-4774-9EBD-1E5EECACD186}
EndGlobalSection
EndGlobal''');
}
// Returns a solution file contents for a solution with plugins A, B, and C
// already added.
void writeSolutionWithPlugins() {
const String pluginSymlinkSubdirPath = r'Flutter\ephemeral\.plugin_symlinks';
const String pluginProjectSubpath = r'windows\plugin.vcxproj';
project.solutionFile.createSync(recursive: true);
project.solutionFile.writeAsStringSync('''
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16
VisualStudioVersion = 16.0.29709.97
MinimumVisualStudioVersion = 10.0.40219.1
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Runner", "Runner.vcxproj", "{5A827760-CF8B-408A-99A3-B6C0AD2271E7}"
ProjectSection(ProjectDependencies) = postProject
{$pluginAGuid} = {$pluginAGuid}
{6419BF13-6ECD-4CD2-9E85-E566A1F03F8F} = {6419BF13-6ECD-4CD2-9E85-E566A1F03F8F}
{$pluginBGuid} = {$pluginBGuid}
{$pluginCGuid} = {$pluginCGuid}
EndProjectSection
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "plugin_a", "$pluginSymlinkSubdirPath\\plugin_a\\$pluginProjectSubpath", "{$pluginAGuid}"
ProjectSection(ProjectDependencies) = postProject
{6419BF13-6ECD-4CD2-9E85-E566A1F03F8F} = {6419BF13-6ECD-4CD2-9E85-E566A1F03F8F}
EndProjectSection
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Flutter Build", "FlutterBuild.vcxproj", "{6419BF13-6ECD-4CD2-9E85-E566A1F03F8F}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "plugin_b", "$pluginSymlinkSubdirPath\\plugin_b\\$pluginProjectSubpath", "{$pluginBGuid}"
ProjectSection(ProjectDependencies) = postProject
{6419BF13-6ECD-4CD2-9E85-E566A1F03F8F} = {6419BF13-6ECD-4CD2-9E85-E566A1F03F8F}
EndProjectSection
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "plugin_c", "$pluginSymlinkSubdirPath\\plugin_c\\$pluginProjectSubpath", "{$pluginCGuid}"
ProjectSection(ProjectDependencies) = postProject
{6419BF13-6ECD-4CD2-9E85-E566A1F03F8F} = {6419BF13-6ECD-4CD2-9E85-E566A1F03F8F}
EndProjectSection
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Flutter Plugins", "Flutter Plugins", "{5C2E738A-1DD3-445A-AAC8-EEB9648DD07C}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|x64 = Debug|x64
Profile|x64 = Profile|x64
Release|x64 = Release|x64
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{5A827760-CF8B-408A-99A3-B6C0AD2271E7}.Debug|x64.ActiveCfg = Debug|x64
{5A827760-CF8B-408A-99A3-B6C0AD2271E7}.Debug|x64.Build.0 = Debug|x64
{5A827760-CF8B-408A-99A3-B6C0AD2271E7}.Profile|x64.ActiveCfg = Profile|x64
{5A827760-CF8B-408A-99A3-B6C0AD2271E7}.Profile|x64.Build.0 = Profile|x64
{5A827760-CF8B-408A-99A3-B6C0AD2271E7}.Release|x64.ActiveCfg = Release|x64
{5A827760-CF8B-408A-99A3-B6C0AD2271E7}.Release|x64.Build.0 = Release|x64
{$pluginAGuid}.Debug|x64.ActiveCfg = Debug|x64
{$pluginAGuid}.Debug|x64.Build.0 = Debug|x64
{$pluginAGuid}.Profile|x64.ActiveCfg = Profile|x64
{$pluginAGuid}.Profile|x64.Build.0 = Profile|x64
{$pluginAGuid}.Release|x64.ActiveCfg = Release|x64
{$pluginAGuid}.Release|x64.Build.0 = Release|x64
{6419BF13-6ECD-4CD2-9E85-E566A1F03F8F}.Debug|x64.ActiveCfg = Debug|x64
{6419BF13-6ECD-4CD2-9E85-E566A1F03F8F}.Debug|x64.Build.0 = Debug|x64
{6419BF13-6ECD-4CD2-9E85-E566A1F03F8F}.Profile|x64.ActiveCfg = Profile|x64
{6419BF13-6ECD-4CD2-9E85-E566A1F03F8F}.Profile|x64.Build.0 = Profile|x64
{6419BF13-6ECD-4CD2-9E85-E566A1F03F8F}.Release|x64.ActiveCfg = Release|x64
{6419BF13-6ECD-4CD2-9E85-E566A1F03F8F}.Release|x64.Build.0 = Release|x64
{$pluginBGuid}.Debug|x64.ActiveCfg = Debug|x64
{$pluginBGuid}.Debug|x64.Build.0 = Debug|x64
{$pluginBGuid}.Profile|x64.ActiveCfg = Profile|x64
{$pluginBGuid}.Profile|x64.Build.0 = Profile|x64
{$pluginBGuid}.Release|x64.ActiveCfg = Release|x64
{$pluginBGuid}.Release|x64.Build.0 = Release|x64
{$pluginCGuid}.Debug|x64.ActiveCfg = Debug|x64
{$pluginCGuid}.Debug|x64.Build.0 = Debug|x64
{$pluginCGuid}.Profile|x64.ActiveCfg = Profile|x64
{$pluginCGuid}.Profile|x64.Build.0 = Profile|x64
{$pluginCGuid}.Release|x64.ActiveCfg = Release|x64
{$pluginCGuid}.Release|x64.Build.0 = Release|x64
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{$pluginCGuid} = {$flutterPluginSolutionFolderGuid}
{$pluginBGuid} = {$flutterPluginSolutionFolderGuid}
{$pluginAGuid} = {$flutterPluginSolutionFolderGuid}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {6C8A8041-10D8-4BEB-B73D-C02BCE62120B}
EndGlobalSection
EndGlobal''');
}
void writeDummyPluginProject(String pluginName, String guid) {
final File pluginProjectFile = project.pluginSymlinkDirectory
.childDirectory(pluginName)
.childDirectory('windows')
.childFile('plugin.vcxproj');
pluginProjectFile.createSync(recursive: true);
pluginProjectFile.writeAsStringSync('''
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup Label="ProjectConfigurations">
<ProjectConfiguration Include="Debug|x64">
<Configuration>Debug</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|x64">
<Configuration>Release</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
</ItemGroup>
<PropertyGroup Label="Globals">
<VCProjectVersion>15.0</VCProjectVersion>
<ProjectGuid>{$guid}</ProjectGuid>
<ProjectName>$pluginName</ProjectName>
<WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>
</PropertyGroup>
<Import Project="\$(VCTargetsPath)\\Microsoft.Cpp.Default.props" />
<PropertyGroup Label="Configuration">
<PlatformToolset>v142</PlatformToolset>
</PropertyGroup>
<Import Project="\$(VCTargetsPath)\\Microsoft.Cpp.props" />
<ImportGroup Label="ExtensionSettings">
</ImportGroup>
<ImportGroup Label="Shared">
</ImportGroup>
<ImportGroup Label="PropertySheets" />
<PropertyGroup Label="UserMacros" />
<PropertyGroup />
<ItemDefinitionGroup />
<ItemGroup>
</ItemGroup>
<Import Project="\$(VCTargetsPath)\\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
</ImportGroup>
</Project>''');
}
// Configures and returns a mock plugin with the given name and GUID in the
// project's plugin symlink directory.
Plugin getMockPlugin(String name, String guid, {bool createProject = true}) {
final MockPlugin plugin = MockPlugin();
when(plugin.platforms).thenReturn(<String, PluginPlatform>{project.pluginConfigKey: null});
when(plugin.name).thenReturn(name);
when(plugin.path).thenReturn(project.pluginSymlinkDirectory.childDirectory(name).path);
if (createProject) {
writeDummyPluginProject(name, guid);
}
return plugin;
}
test('Adding the first plugin to a solution adds the expected references', () async {
writeSolutionWithoutPlugins();
final List<Plugin> plugins = <Plugin>[
getMockPlugin('plugin_a', pluginAGuid),
];
await VisualStudioSolutionUtils(project: project, fileSystem: fileSystem).updatePlugins(plugins);
final String newSolutionContents = project.solutionFile.readAsStringSync();
// Check for:
// - Plugin project.
final String pluginSubpath = fileSystem.path.join('Flutter', 'ephemeral', '.plugin_symlinks', 'plugin_a', 'windows', 'plugin.vcxproj');
expect(newSolutionContents, contains('Project("{$solutionTypeGuidVcxproj}") = "plugin_a", "$pluginSubpath", "{$pluginAGuid}"'));
// - A dependency on the plugin project (from the Runner).
expect(newSolutionContents, contains('{$pluginAGuid} = {$pluginAGuid}'));
// - Plugin project configurations.
expect(newSolutionContents, contains('{$pluginAGuid}.Debug|x64.ActiveCfg = Debug|x64'));
expect(newSolutionContents, contains('{$pluginAGuid}.Debug|x64.Build.0 = Debug|x64'));
expect(newSolutionContents, contains('{$pluginAGuid}.Profile|x64.ActiveCfg = Profile|x64'));
expect(newSolutionContents, contains('{$pluginAGuid}.Profile|x64.Build.0 = Profile|x64'));
expect(newSolutionContents, contains('{$pluginAGuid}.Release|x64.ActiveCfg = Release|x64'));
expect(newSolutionContents, contains('{$pluginAGuid}.Release|x64.Build.0 = Release|x64'));
// - A plugin folder, and a child reference for the plugin.
expect(newSolutionContents, contains('Project("{$solutionTypeGuidFolder}") = "Flutter Plugins", "Flutter Plugins", "{$flutterPluginSolutionFolderGuid}"'));
expect(newSolutionContents, contains('{$pluginAGuid} = {$flutterPluginSolutionFolderGuid}'));
});
test('Removing a plugin removes entries as expected', () async {
writeSolutionWithPlugins();
final List<Plugin> plugins = <Plugin>[
getMockPlugin('plugin_a', pluginAGuid),
getMockPlugin('plugin_c', pluginCGuid),
];
await VisualStudioSolutionUtils(project: project, fileSystem: fileSystem).updatePlugins(plugins);
final String newSolutionContents = project.solutionFile.readAsStringSync();
// There should be no references to plugin B anywhere.
expect(newSolutionContents.contains(pluginBGuid), false);
// All the plugin A & C references should still be present in modified
// sections.
for (final String guid in <String>[pluginAGuid, pluginCGuid]) {
expect(newSolutionContents, contains('{$guid} = {$guid}'));
expect(newSolutionContents, contains('{$guid}.Debug|x64.ActiveCfg = Debug|x64'));
expect(newSolutionContents, contains('{$guid} = {$flutterPluginSolutionFolderGuid}'));
}
});
test('Removing all plugins works', () async {
writeSolutionWithPlugins();
final List<Plugin> plugins = <Plugin>[
];
await VisualStudioSolutionUtils(project: project, fileSystem: fileSystem).updatePlugins( plugins);
final String newSolutionContents = project.solutionFile.readAsStringSync();
// There should be no references to any of the plugins.
expect(newSolutionContents.contains(pluginAGuid), false);
expect(newSolutionContents.contains(pluginBGuid), false);
expect(newSolutionContents.contains(pluginCGuid), false);
// Nor any plugins in the Flutter Plugins folder.
expect(newSolutionContents.contains('= {$flutterPluginSolutionFolderGuid}'), false);
});
test('Adjusting the plugin list by adding and removing adjusts entries as expected', () async {
writeSolutionWithPlugins();
final List<Plugin> plugins = <Plugin>[
getMockPlugin('plugin_b', pluginBGuid),
getMockPlugin('plugin_c', pluginCGuid),
getMockPlugin('plugin_d', pluginDGuid),
];
await VisualStudioSolutionUtils(project: project, fileSystem: fileSystem).updatePlugins(plugins);
final String newSolutionContents = project.solutionFile.readAsStringSync();
// There should be no references to plugin A anywhere.
expect(newSolutionContents.contains(pluginAGuid), false);
// All the plugin B & C references should still be present in modified
// sections.
for (final String guid in <String>[pluginBGuid, pluginCGuid]) {
expect(newSolutionContents, contains('{$guid} = {$guid}'));
expect(newSolutionContents, contains('{$guid}.Debug|x64.ActiveCfg = Debug|x64'));
expect(newSolutionContents, contains('{$guid} = {$flutterPluginSolutionFolderGuid}'));
}
// All the plugin D values should be added:
// - Plugin project.
final String pluginSubpath = fileSystem.path.join('Flutter', 'ephemeral', '.plugin_symlinks', 'plugin_d', 'windows', 'plugin.vcxproj');
expect(newSolutionContents, contains('Project("{$solutionTypeGuidVcxproj}") = "plugin_d", "$pluginSubpath", "{$pluginDGuid}"'));
// - A dependency on the plugin project (from the Runner).
expect(newSolutionContents, contains('{$pluginDGuid} = {$pluginDGuid}'));
// - Plugin project configurations.
expect(newSolutionContents, contains('{$pluginDGuid}.Debug|x64.ActiveCfg = Debug|x64'));
expect(newSolutionContents, contains('{$pluginDGuid}.Debug|x64.Build.0 = Debug|x64'));
expect(newSolutionContents, contains('{$pluginDGuid}.Profile|x64.ActiveCfg = Profile|x64'));
expect(newSolutionContents, contains('{$pluginDGuid}.Profile|x64.Build.0 = Profile|x64'));
expect(newSolutionContents, contains('{$pluginDGuid}.Release|x64.ActiveCfg = Release|x64'));
expect(newSolutionContents, contains('{$pluginDGuid}.Release|x64.Build.0 = Release|x64'));
// - A child reference for the plugin in the Flutter Plugins folder.
expect(newSolutionContents, contains('{$pluginDGuid} = {$flutterPluginSolutionFolderGuid}'));
});
test('Adding plugins doesn\'t create duplicate entries', () async {
writeSolutionWithPlugins();
final List<Plugin> plugins = <Plugin>[
getMockPlugin('plugin_a', pluginAGuid),
getMockPlugin('plugin_b', pluginBGuid),
getMockPlugin('plugin_c', pluginCGuid),
getMockPlugin('plugin_d', pluginDGuid),
];
await VisualStudioSolutionUtils(project: project, fileSystem: fileSystem).updatePlugins(plugins);
final String newSolutionContents = project.solutionFile.readAsStringSync();
// There should only be:
// - one Flutter Plugins folder.
expect('Project("{$solutionTypeGuidFolder}")'.allMatches(newSolutionContents).length, 1);
// - one copy of plugin A's project.
expect('Project("{$solutionTypeGuidVcxproj}") = "plugin_a"'.allMatches(newSolutionContents).length, 1);
// - one copy of plugin A's configuration entries.
expect('{$pluginAGuid}.Debug|x64.ActiveCfg = Debug|x64'.allMatches(newSolutionContents).length, 1);
// - one dependency from the Runner to plugin A.
expect('{$pluginAGuid} = {$pluginAGuid}'.allMatches(newSolutionContents).length, 1);
// - one copy of plugin A in Flutter Plugins.
expect('{$pluginAGuid} = {$flutterPluginSolutionFolderGuid}'.allMatches(newSolutionContents).length, 1);
});
test('Adding plugins doesn\'t change ordering', () async {
writeSolutionWithPlugins();
final List<Plugin> plugins = <Plugin>[
getMockPlugin('plugin_a', pluginAGuid),
getMockPlugin('plugin_b', pluginBGuid),
getMockPlugin('plugin_c', pluginCGuid),
getMockPlugin('plugin_d', pluginDGuid),
];
await VisualStudioSolutionUtils(project: project, fileSystem: fileSystem).updatePlugins(plugins);
final String newSolutionContents = project.solutionFile.readAsStringSync();
// Plugin A should still be before Flutter Build in the Runner dependencies.
expect(newSolutionContents.indexOf('{$pluginAGuid} = {$pluginAGuid}'),
lessThan(newSolutionContents.indexOf('{6419BF13-6ECD-4CD2-9E85-E566A1F03F8F} = {6419BF13-6ECD-4CD2-9E85-E566A1F03F8F}')));
// ... and in the build configuration list.
expect(newSolutionContents.indexOf('{$pluginAGuid}.Debug|x64.ActiveCfg = Debug|x64'),
lessThan(newSolutionContents.indexOf('{6419BF13-6ECD-4CD2-9E85-E566A1F03F8F}.Debug|x64.ActiveCfg = Debug|x64')));
// And plugin C should still be before plugin A in the Flutter Plugins nesting list.
expect(newSolutionContents.indexOf('{$pluginCGuid} = {$flutterPluginSolutionFolderGuid}'),
lessThan(newSolutionContents.indexOf('{$pluginAGuid} = {$flutterPluginSolutionFolderGuid}')));
});
test('Updating solution preserves BOM', () async {
writeSolutionWithPlugins();
final List<Plugin> plugins = <Plugin>[];
await VisualStudioSolutionUtils(project: project, fileSystem: fileSystem).updatePlugins(plugins);
// Visual Studio expects sln files to start with a BOM.
final List<int> solutionStartingBytes = project.solutionFile.readAsBytesSync().take(3).toList();
final List<int> bomBytes = utf8.encode(String.fromCharCode(unicodeBomCharacterRune));
expect(solutionStartingBytes, bomBytes);
});
test('Updating solution dosen\'t introduce unexpected whitespace', () async {
writeSolutionWithPlugins();
final List<Plugin> plugins = <Plugin>[
getMockPlugin('plugin_a', pluginAGuid),
getMockPlugin('plugin_b', pluginBGuid),
];
await VisualStudioSolutionUtils(project: project, fileSystem: fileSystem).updatePlugins(plugins);
final String newSolutionContents = project.solutionFile.readAsStringSync();
// Project, EndProject, Global, and EndGlobal should be at the start of
// lines.
expect(RegExp(r'^[ \t]+Project\(', multiLine: true).hasMatch(newSolutionContents), false);
expect(RegExp(r'^[ \t]+EndProject\s*$', multiLine: true).hasMatch(newSolutionContents), false);
expect(RegExp(r'^[ \t]+Global\s*$', multiLine: true).hasMatch(newSolutionContents), false);
expect(RegExp(r'^[ \t]+EndGlobal\s*$', multiLine: true).hasMatch(newSolutionContents), false);
// ProjectSection, GlobalSection, and their ends should be indented
// exactly one tab.
expect(RegExp(r'^([ \t]+\t|\t[ \t]+)ProjectSection\(', multiLine: true).hasMatch(newSolutionContents), false);
expect(RegExp(r'^([ \t]+\t|\t[ \t]+)EndProjectSection\s*$', multiLine: true).hasMatch(newSolutionContents), false);
expect(RegExp(r'^([ \t]+\t|\t[ \t]+)GlobalSection\(\s*$', multiLine: true).hasMatch(newSolutionContents), false);
expect(RegExp(r'^([ \t]+\t|\t[ \t]+)EndGlobalSection\s*$', multiLine: true).hasMatch(newSolutionContents), false);
});
test('A plugin without a project exits without crashing', () async {
writeSolutionWithoutPlugins();
final List<Plugin> plugins = <Plugin>[
getMockPlugin('plugin_a', pluginAGuid, createProject: false),
];
expect(() => VisualStudioSolutionUtils(project: project, fileSystem: fileSystem).updatePlugins(plugins),
throwsToolExit());
});
test('A Windows project with a missing Runner.sln file throws a ToolExit', () async {
final MockWindowsProject windowsProject = MockWindowsProject();
final File file = fileSystem.file('does_not_exist');
expect(file, isNot(exists));
when(windowsProject.solutionFile).thenReturn(file);
expect(() async => await VisualStudioSolutionUtils(project: project, fileSystem: fileSystem)
.updatePlugins(<Plugin>[]), throwsToolExit());
});
});
}
class MockWindowsProject extends Mock implements WindowsProject {}
class MockPlugin extends Mock implements Plugin {}
...@@ -17,7 +17,7 @@ import '../../src/mocks.dart'; ...@@ -17,7 +17,7 @@ import '../../src/mocks.dart';
const String programFilesPath = r'C:\Program Files (x86)'; const String programFilesPath = r'C:\Program Files (x86)';
const String visualStudioPath = programFilesPath + r'\Microsoft Visual Studio\2017\Community'; const String visualStudioPath = programFilesPath + r'\Microsoft Visual Studio\2017\Community';
const String vcvarsPath = visualStudioPath + r'\VC\Auxiliary\Build\vcvars64.bat'; 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 vswherePath = programFilesPath + r'\Microsoft Visual Studio\Installer\vswhere.exe';
final Platform windowsPlatform = FakePlatform( final Platform windowsPlatform = FakePlatform(
...@@ -71,6 +71,7 @@ const Map<String, dynamic> _missingStatusResponse = <String, dynamic>{ ...@@ -71,6 +71,7 @@ const Map<String, dynamic> _missingStatusResponse = <String, dynamic>{
const List<String> _requirements = <String>[ const List<String> _requirements = <String>[
'Microsoft.VisualStudio.Workload.NativeDesktop', 'Microsoft.VisualStudio.Workload.NativeDesktop',
'Microsoft.VisualStudio.Component.VC.Tools.x86.x64', 'Microsoft.VisualStudio.Component.VC.Tools.x86.x64',
'Microsoft.VisualStudio.Component.VC.CMake.Project',
]; ];
// Sets up the mock environment so that searching for Visual Studio with // Sets up the mock environment so that searching for Visual Studio with
...@@ -85,7 +86,7 @@ void setMockVswhereResponse( ...@@ -85,7 +86,7 @@ void setMockVswhereResponse(
String responseOverride, String responseOverride,
]) { ]) {
fileSystem.file(vswherePath).createSync(recursive: true); fileSystem.file(vswherePath).createSync(recursive: true);
fileSystem.file(vcvarsPath).createSync(recursive: true); fileSystem.file(cmakePath).createSync(recursive: true);
final String finalResponse = responseOverride final String finalResponse = responseOverride
?? json.encode(<Map<String, dynamic>>[response]); ?? json.encode(<Map<String, dynamic>>[response]);
final List<String> requirementArguments = requiredComponents == null final List<String> requirementArguments = requiredComponents == null
...@@ -249,7 +250,7 @@ void main() { ...@@ -249,7 +250,7 @@ void main() {
expect(visualStudio.isInstalled, false); expect(visualStudio.isInstalled, false);
}); });
testWithoutContext('vcvarsPath returns null when vswhere is missing', () { testWithoutContext('cmakePath returns null when vswhere is missing', () {
final MockProcessManager mockProcessManager = MockProcessManager(); final MockProcessManager mockProcessManager = MockProcessManager();
when(mockProcessManager.runSync( when(mockProcessManager.runSync(
any, any,
...@@ -265,7 +266,7 @@ void main() { ...@@ -265,7 +266,7 @@ void main() {
processManager: mockProcessManager, processManager: mockProcessManager,
); );
expect(visualStudio.vcvarsPath, isNull); expect(visualStudio.cmakePath, isNull);
}); });
testWithoutContext( testWithoutContext(
...@@ -587,7 +588,7 @@ void main() { ...@@ -587,7 +588,7 @@ void main() {
expect(visualStudio.hasNecessaryComponents, false); expect(visualStudio.hasNecessaryComponents, false);
}); });
testWithoutContext('vcvarsPath returns null when VS is present but missing components', () { testWithoutContext('cmakePath returns null when VS is present but missing components', () {
final VisualStudioFixture fixture = setUpVisualStudio(); final VisualStudioFixture fixture = setUpVisualStudio();
final VisualStudio visualStudio = fixture.visualStudio; final VisualStudio visualStudio = fixture.visualStudio;
...@@ -607,10 +608,10 @@ void main() { ...@@ -607,10 +608,10 @@ void main() {
fixture.processManager, fixture.processManager,
); );
expect(visualStudio.vcvarsPath, isNull); expect(visualStudio.cmakePath, isNull);
}); });
testWithoutContext('vcvarsPath returns null when VS is present but with require components but installation is faulty', () { testWithoutContext('cmakePath returns null when VS is present but with require components but installation is faulty', () {
final VisualStudioFixture fixture = setUpVisualStudio(); final VisualStudioFixture fixture = setUpVisualStudio();
final VisualStudio visualStudio = fixture.visualStudio; final VisualStudio visualStudio = fixture.visualStudio;
...@@ -627,7 +628,7 @@ void main() { ...@@ -627,7 +628,7 @@ void main() {
fixture.processManager, fixture.processManager,
); );
expect(visualStudio.vcvarsPath, isNull); expect(visualStudio.cmakePath, isNull);
}); });
testWithoutContext('hasNecessaryComponents returns false when VS is present with required components but installation is faulty', () { testWithoutContext('hasNecessaryComponents returns false when VS is present with required components but installation is faulty', () {
...@@ -676,7 +677,7 @@ void main() { ...@@ -676,7 +677,7 @@ void main() {
expect(visualStudio.fullVersion, equals('16.2.29306.81')); expect(visualStudio.fullVersion, equals('16.2.29306.81'));
}); });
testWithoutContext('vcvarsPath returns null when VS is present but when vswhere returns invalid JSON', () { testWithoutContext('cmakePath returns null when VS is present but when vswhere returns invalid JSON', () {
final VisualStudioFixture fixture = setUpVisualStudio(); final VisualStudioFixture fixture = setUpVisualStudio();
final VisualStudio visualStudio = fixture.visualStudio; final VisualStudio visualStudio = fixture.visualStudio;
...@@ -696,7 +697,7 @@ void main() { ...@@ -696,7 +697,7 @@ void main() {
fixture.processManager, fixture.processManager,
); );
expect(visualStudio.vcvarsPath, isNull); expect(visualStudio.cmakePath, isNull);
}); });
testWithoutContext('Everything returns good values when VS is present with all components', () { testWithoutContext('Everything returns good values when VS is present with all components', () {
...@@ -722,7 +723,7 @@ void main() { ...@@ -722,7 +723,7 @@ void main() {
expect(visualStudio.isInstalled, true); expect(visualStudio.isInstalled, true);
expect(visualStudio.isAtLeastMinimumVersion, true); expect(visualStudio.isAtLeastMinimumVersion, true);
expect(visualStudio.hasNecessaryComponents, true); expect(visualStudio.hasNecessaryComponents, true);
expect(visualStudio.vcvarsPath, equals(vcvarsPath)); expect(visualStudio.cmakePath, equals(cmakePath));
}); });
testWithoutContext('Metadata is for compatible version when latest is missing components', () { testWithoutContext('Metadata is for compatible version when latest is missing components', () {
......
...@@ -71,7 +71,7 @@ void main() { ...@@ -71,7 +71,7 @@ void main() {
globals.fs.file('pubspec.yaml').createSync(); globals.fs.file('pubspec.yaml').createSync();
globals.fs.file('.packages').createSync(); globals.fs.file('.packages').createSync();
globals.fs.directory('windows').createSync(); globals.fs.directory('windows').createSync();
globals.fs.file(globals.fs.path.join('windows', 'Runner.sln')).createSync(); globals.fs.file(globals.fs.path.join('windows', 'CMakeLists.txt')).createSync();
final FlutterProject flutterProject = FlutterProject.current(); final FlutterProject flutterProject = FlutterProject.current();
expect(WindowsDevice().isSupportedForProject(flutterProject), true); expect(WindowsDevice().isSupportedForProject(flutterProject), true);
...@@ -91,7 +91,7 @@ void main() { ...@@ -91,7 +91,7 @@ void main() {
ProcessManager: () => FakeProcessManager.any(), ProcessManager: () => FakeProcessManager.any(),
}); });
testUsingContext('isSupportedForProject is false with no Runner.sln', () async { testUsingContext('isSupportedForProject is false with no build file', () async {
globals.fs.file('pubspec.yaml').createSync(); globals.fs.file('pubspec.yaml').createSync();
globals.fs.file('.packages').createSync(); globals.fs.file('.packages').createSync();
globals.fs.directory('windows').createSync(); globals.fs.directory('windows').createSync();
......
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