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 @@
// Use of this source code is governed by a BSD-style license that can be
// 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.
String getCmakeExecutableName(LinuxProject project) {
String getCmakeExecutableName(CmakeBasedProject project) {
if (!project.cmakeFile.existsSync()) {
return null;
}
......@@ -21,24 +21,30 @@ String getCmakeExecutableName(LinuxProject project) {
return null;
}
String _escapeBackslashes(String s) {
return s.replaceAll(r'\', r'\\');
}
/// Writes a generated CMake configuration file for [project], including
/// variables expected by the build template and an environment variable list
/// 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,
// 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('''
# Generated code do not commit.
set(FLUTTER_ROOT "$flutterRoot")
set(PROJECT_DIR "${project.project.directory.path}")
file(TO_CMAKE_PATH "$escapedFlutterRoot" FLUTTER_ROOT)
file(TO_CMAKE_PATH "$escapedProjectDir" PROJECT_DIR)
# Environment variables to pass to tool_backend.sh
list(APPEND FLUTTER_TOOL_ENVIRONMENT
"FLUTTER_ROOT=\\"\${FLUTTER_ROOT}\\""
"PROJECT_DIR=\\"\${PROJECT_DIR}\\""
"FLUTTER_ROOT=\\"$escapedFlutterRoot\\""
"PROJECT_DIR=\\"$escapedProjectDir\\""
''');
for (final String key in environment.keys) {
final String value = environment[key];
final String value = _escapeBackslashes(environment[key]);
buffer.writeln(' "$key=\\"$value\\""');
}
buffer.writeln(')');
......
......@@ -7,9 +7,9 @@ import 'package:meta/meta.dart';
import '../application_package.dart';
import '../base/file_system.dart';
import '../build_info.dart';
import '../cmake.dart';
import '../globals.dart' as globals;
import '../project.dart';
import 'cmake.dart';
abstract class LinuxApp extends ApplicationPackage {
LinuxApp({@required String projectBundleId}) : super(id: projectBundleId);
......@@ -52,7 +52,7 @@ class PrebuiltLinuxApp 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;
......@@ -68,5 +68,5 @@ class BuildableLinuxApp extends LinuxApp {
}
@override
String get name => project.project.manifest.appName;
String get name => project.parent.manifest.appName;
}
......@@ -10,10 +10,10 @@ import '../base/process.dart';
import '../base/utils.dart';
import '../build_info.dart';
import '../cache.dart';
import '../cmake.dart';
import '../globals.dart' as globals;
import '../plugins.dart';
import '../project.dart';
import 'cmake.dart';
/// Builds the Linux project through the Makefile.
Future<void> buildLinux(
......@@ -39,7 +39,7 @@ Future<void> buildLinux(
}
writeGeneratedCmakeConfig(Cache.flutterRoot, linuxProject, environmentConfig);
createPluginSymlinks(linuxProject.project);
createPluginSymlinks(linuxProject.parent);
final Status status = globals.logger.startProgress(
'Building Linux application...',
......
......@@ -6,6 +6,7 @@ import 'dart:async';
import 'package:meta/meta.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 'android/gradle.dart';
......@@ -17,8 +18,6 @@ import 'features.dart';
import 'globals.dart' as globals;
import 'platform_plugins.dart';
import 'project.dart';
import 'windows/property_sheet.dart';
import 'windows/visual_studio_solution_utils.dart';
void _renderTemplateToFile(String template, dynamic context, String filePath) {
final String renderedTemplate = globals.templateRenderer
......@@ -801,7 +800,7 @@ const String _cppPluginRegistryImplementationTemplate = '''
#include "generated_plugin_registrant.h"
{{#plugins}}
#include <{{filename}}.h>
#include <{{name}}/{{filename}}.h>
{{/plugins}}
void RegisterPlugins(flutter::PluginRegistry* registry) {
......@@ -848,7 +847,7 @@ void fl_register_plugins(FlPluginRegistry* registry) {
}
''';
const String _linuxPluginCmakefileTemplate = r'''
const String _pluginCmakefileTemplate = r'''
#
# Generated file, do not edit.
#
......@@ -862,7 +861,7 @@ list(APPEND FLUTTER_PLUGIN_LIST
set(PLUGIN_BUNDLED_LIBRARIES)
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)
list(APPEND PLUGIN_BUNDLED_LIBRARIES $<TARGET_FILE:${plugin}_plugin>)
list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries})
......@@ -917,6 +916,7 @@ Future<void> _writeLinuxPluginFiles(FlutterProject project, List<Plugin> plugins
// that file's directory.
final String makefileDirPath = project.linux.cmakeFile.parent.absolute.path;
final Map<String, dynamic> context = <String, dynamic>{
'os': 'linux',
'plugins': linuxPlugins,
'pluginsDir': globals.fs.path.relative(
project.linux.pluginSymlinkDirectory.absolute.path,
......@@ -924,7 +924,7 @@ Future<void> _writeLinuxPluginFiles(FlutterProject project, List<Plugin> plugins
),
};
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 {
......@@ -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(
_linuxPluginCmakefileTemplate,
_pluginCmakefileTemplate,
templateContext,
destinationFile.path,
);
......@@ -984,11 +984,22 @@ List<Plugin> _filterNativePlugins(List<Plugin> plugins, String platformKey) {
Future<void> _writeWindowsPluginFiles(FlutterProject project, List<Plugin> plugins) async {
final List<Plugin>nativePlugins = _filterNativePlugins(plugins, 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>{
'os': 'windows',
'plugins': windowsPlugins,
'pluginsDir': cmakePathContext.joinAll(relativePathComponents),
};
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 {
......@@ -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 {
final List<Map<String, dynamic>> webPlugins = _extractPlatformMaps(plugins, WebPlugin.kConfigKey);
final Map<String, dynamic> context = <String, dynamic>{
......@@ -1159,9 +1156,6 @@ Future<void> injectPlugins(FlutterProject project, {bool checkProjects = false})
}
if (featureFlags.isWindowsEnabled && project.windows.existsSync()) {
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]) {
if (!project.isModule && (!checkProjects || subproject.existsSync())) {
......
......@@ -329,6 +329,27 @@ abstract class XcodeBasedProject {
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.
///
/// Instances will reflect the contents of the `ios/` sub-folder of
......@@ -995,18 +1016,28 @@ class MacOSProject extends FlutterProjectPlatform implements XcodeBasedProject {
}
/// The Windows sub project
class WindowsProject extends FlutterProjectPlatform {
WindowsProject._(this.project);
class WindowsProject extends FlutterProjectPlatform implements CmakeBasedProject {
WindowsProject._(this.parent);
final FlutterProject project;
@override
final FlutterProject parent;
@override
String get pluginConfigKey => WindowsPlugin.kConfigKey;
@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
/// possible, files that are edited by Flutter tooling after initial project
......@@ -1018,24 +1049,6 @@ class WindowsProject extends FlutterProjectPlatform {
/// checked in should live here.
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.
Directory get pluginSymlinkDirectory => ephemeralDirectory.childDirectory('.plugin_symlinks');
......@@ -1043,15 +1056,16 @@ class WindowsProject extends FlutterProjectPlatform {
}
/// The Linux sub project.
class LinuxProject extends FlutterProjectPlatform {
LinuxProject._(this.project);
class LinuxProject extends FlutterProjectPlatform implements CmakeBasedProject {
LinuxProject._(this.parent);
final FlutterProject project;
@override
final FlutterProject parent;
@override
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
/// possible, files that are edited by Flutter tooling after initial project
......@@ -1066,14 +1080,13 @@ class LinuxProject extends FlutterProjectPlatform {
@override
bool existsSync() => _editableDirectory.existsSync();
/// The Linux project CMake specification.
@override
File get cmakeFile => _editableDirectory.childFile('CMakeLists.txt');
/// Contains definitions for FLUTTER_ROOT, LOCAL_ENGINE, and more flags for
/// the build.
@override
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');
/// The directory to write plugin symlinks.
......
......@@ -5,10 +5,10 @@
import 'package:meta/meta.dart';
import '../application_package.dart';
import '../base/common.dart';
import '../base/file_system.dart';
import '../base/utils.dart';
import '../build_info.dart';
import '../cmake.dart';
import '../globals.dart' as globals;
import '../project.dart';
......@@ -55,24 +55,21 @@ class PrebuiltWindowsApp extends WindowsApp {
class BuildableWindowsApp extends WindowsApp {
BuildableWindowsApp({
@required this.project,
}) : super(projectBundleId: project.project.manifest.appName);
}) : super(projectBundleId: project.parent.manifest.appName);
final WindowsProject project;
@override
String executable(BuildMode buildMode) {
final File exeNameFile = project.nameFile;
if (!exeNameFile.existsSync()) {
throwToolExit('Failed to find Windows executable name');
}
final String binaryName = getCmakeExecutableName(project);
return globals.fs.path.join(
getWindowsBuildDirectory(),
'x64',
'runner',
toTitleCase(getNameForBuildMode(buildMode)),
'Runner',
exeNameFile.readAsStringSync().trim());
'$binaryName.exe',
);
}
@override
String get name => project.project.manifest.appName;
String get name => project.parent.manifest.appName;
}
......@@ -10,18 +10,23 @@ import '../base/process.dart';
import '../base/utils.dart';
import '../build_info.dart';
import '../cache.dart';
import '../cmake.dart';
import '../globals.dart' as globals;
import '../plugins.dart';
import '../project.dart';
import 'property_sheet.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.
Future<void> buildWindows(WindowsProject windowsProject, BuildInfo buildInfo, {
String target,
VisualStudio visualStudioOverride,
}) async {
if (!windowsProject.solutionFile.existsSync()) {
if (!windowsProject.cmakeFile.existsSync()) {
throwToolExit(
'No Windows desktop project configured. '
'See https://github.com/flutter/flutter/wiki/Desktop-shells#create '
......@@ -43,8 +48,8 @@ Future<void> buildWindows(WindowsProject windowsProject, BuildInfo buildInfo, {
}
// Ensure that necessary emphemeral files are generated and up to date.
_writeGeneratedFlutterProperties(windowsProject, buildInfo, target);
createPluginSymlinks(windowsProject.project);
_writeGeneratedFlutterConfig(windowsProject, buildInfo, target);
createPluginSymlinks(windowsProject.parent);
final VisualStudio visualStudio = visualStudioOverride ?? VisualStudio(
fileSystem: globals.fs,
......@@ -52,57 +57,88 @@ Future<void> buildWindows(WindowsProject windowsProject, BuildInfo buildInfo, {
logger: globals.logger,
processManager: globals.processManager,
);
final String vcvarsScript = visualStudio.vcvarsPath;
if (vcvarsScript == null) {
final String cmakePath = visualStudio.cmakePath;
if (cmakePath == null) {
throwToolExit('Unable to find suitable Visual Studio toolchain. '
'Please run `flutter doctor` for more details.');
}
final String buildScript = globals.fs.path.join(
Cache.flutterRoot,
'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 String buildModeName = getNameForBuildMode(buildInfo.mode ?? BuildMode.release);
final Directory buildDirectory = globals.fs.directory(getWindowsBuildDirectory());
final Status status = globals.logger.startProgress(
'Building Windows application...',
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;
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(
<String>[
buildScript,
vcvarsScript,
globals.fs.path.basename(solutionPath),
configuration,
cmakePath,
'--build',
buildDir.path,
'--config',
toTitleCase(buildModeName),
'--target',
'INSTALL',
if (globals.logger.isVerbose)
'--verbose'
],
environment: <String, String>{
if (globals.logger.isVerbose)
'VERBOSE_SCRIPT_LOGGING': 'true'
},
workingDirectory: globals.fs.path.dirname(solutionPath),
trace: true,
);
} finally {
status.cancel();
} on ArgumentError {
throwToolExit("cmake not found. Run 'flutter doctor' for more information.");
}
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.
void _writeGeneratedFlutterProperties(
/// Writes the generated CMake file with the configuration for the given build.
void _writeGeneratedFlutterConfig(
WindowsProject windowsProject,
BuildInfo buildInfo,
String target,
......@@ -110,7 +146,7 @@ void _writeGeneratedFlutterProperties(
final Map<String, String> environment = <String, String>{
'FLUTTER_ROOT': Cache.flutterRoot,
'FLUTTER_EPHEMERAL_DIR': windowsProject.ephemeralDirectory.path,
'PROJECT_DIR': windowsProject.project.directory.path,
'PROJECT_DIR': windowsProject.parent.directory.path,
if (target != null)
'FLUTTER_TARGET': target,
...buildInfo.toEnvironmentConfig(),
......@@ -121,10 +157,7 @@ void _writeGeneratedFlutterProperties(
environment['FLUTTER_ENGINE'] = globals.fs.path.dirname(globals.fs.path.dirname(engineOutPath));
environment['LOCAL_ENGINE'] = globals.fs.path.basename(engineOutPath);
}
final File propsFile = windowsProject.generatedPropertySheetFile;
propsFile.createSync(recursive: true);
propsFile.writeAsStringSync(PropertySheet(environmentVariables: environment).toString());
writeGeneratedCmakeConfig(Cache.flutterRoot, windowsProject, environment);
}
// 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 {
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.
String get vcvarsPath {
String get cmakePath {
final Map<String, dynamic> details = _usableVisualStudioDetails;
if (details.isEmpty) {
return null;
}
return _fileSystem.path.join(
return _fileSystem.path.joinAll(<String>[
_usableVisualStudioDetails[_installationPathKey] as String,
'VC',
'Auxiliary',
'Build',
'vcvars64.bat',
);
'Common7',
'IDE',
'CommonExtensions',
'Microsoft',
'CMake',
'CMake',
'bin',
'cmake.exe',
]);
}
/// The major version of the Visual Studio install, as an integer.
......@@ -212,6 +216,8 @@ class VisualStudio {
return <String, String>{
// The C++ toolchain required by the template.
'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);
}
}
}
<?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>
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_
#defineFLUTTER_PLUGIN_{{pluginClassCapitalSnakeCase}}_H_
#define FLUTTER_PLUGIN_{{pluginClassCapitalSnakeCase}}_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>
#include "{{projectName}}_plugin.h"
#include "include/{{projectName}}/{{projectName}}_plugin.h"
// This must be included before many other Windows headers.
#include <windows.h>
......
......@@ -122,9 +122,10 @@
"templates/app/web/index.html.tmpl",
"templates/app/web/manifest.json.tmpl",
"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/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.h",
"templates/app/windows.tmpl/runner/main.cpp",
......@@ -140,11 +141,6 @@
"templates/app/windows.tmpl/runner/win32_window.h",
"templates/app/windows.tmpl/runner/window_configuration.cpp.tmpl",
"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-swift",
"templates/cocoapods/Podfile-macos",
......@@ -281,11 +277,9 @@
"templates/plugin/README.md.tmpl",
"templates/plugin/test/projectName_test.dart.tmpl",
"templates/plugin/windows.tmpl/.gitignore",
"templates/plugin/windows.tmpl/plugin.vcxproj.filters",
"templates/plugin/windows.tmpl/plugin.vcxproj.tmpl",
"templates/plugin/windows.tmpl/PluginInfo.props.tmpl",
"templates/plugin/windows.tmpl/CMakeLists.txt.tmpl",
"templates/plugin/windows.tmpl/include/projectName.tmpl/projectName_plugin.h.tmpl",
"templates/plugin/windows.tmpl/projectName_plugin.cpp.tmpl",
"templates/plugin/windows.tmpl/projectName_plugin.h.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';
import 'package:flutter_tools/src/base/platform.dart';
import 'package:flutter_tools/src/base/utils.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_linux.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:process/process.dart';
......@@ -267,7 +267,7 @@ void main() {
FeatureFlags: () => TestFeatureFlags(isLinuxEnabled: true),
});
testUsingContext('Linux build configures Makefile exports', () async {
testUsingContext('Linux build configures CMake exports', () async {
final BuildCommand command = BuildCommand();
setUpMockProjectFilesForBuild();
processManager = FakeProcessManager.list(<FakeCommand>[
......@@ -305,8 +305,8 @@ void main() {
final List<String> configLines = cmakeConfig.readAsLinesSync();
expect(configLines, containsAll(<String>[
'set(FLUTTER_ROOT "$_kTestFlutterRoot")',
'set(PROJECT_DIR "${fileSystem.currentDirectory.path}")',
'file(TO_CMAKE_PATH "$_kTestFlutterRoot" FLUTTER_ROOT)',
'file(TO_CMAKE_PATH "${fileSystem.currentDirectory.path}" PROJECT_DIR)',
' "DART_DEFINES=\\"foo.bar%3D2,fizz.far%3D3\\""',
' "DART_OBFUSCATION=\\"true\\""',
' "EXTRA_FRONT_END_OPTIONS=\\"--enable-experiment%3Dnon-nullable\\""',
......@@ -314,8 +314,8 @@ void main() {
' "SPLIT_DEBUG_INFO=\\"foo/\\""',
' "TRACK_WIDGET_CREATION=\\"true\\""',
' "TREE_SHAKE_ICONS=\\"true\\""',
' "FLUTTER_ROOT=\\"\${FLUTTER_ROOT}\\""',
' "PROJECT_DIR=\\"\${PROJECT_DIR}\\""',
' "FLUTTER_ROOT=\\"$_kTestFlutterRoot\\""',
' "PROJECT_DIR=\\"${fileSystem.currentDirectory.path}\\""',
' "FLUTTER_TARGET=\\"lib/other.dart\\""',
' "BUNDLE_SKSL_PATH=\\"foo/bar.sksl.json\\""',
]));
......
......@@ -777,7 +777,7 @@ void main() {
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>{
FeatureFlags: () => TestFeatureFlags(isWindowsEnabled: true),
});
......@@ -792,7 +792,7 @@ void main() {
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>{
FeatureFlags: () => TestFeatureFlags(isWindowsEnabled: false),
});
......@@ -807,7 +807,7 @@ void main() {
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);
validatePubspecForPlugin(projectDir: projectDir.absolute.path, expectedPlatforms: const <String>[
'windows'
......@@ -827,7 +827,7 @@ void main() {
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);
}, overrides: <Type, Generator>{
FeatureFlags: () => TestFeatureFlags(isWindowsEnabled: false),
......
......@@ -80,10 +80,9 @@ void main() {
when(windowsProject.pluginConfigKey).thenReturn('windows');
final Directory windowsManagedDirectory = flutterProject.directory.childDirectory('windows').childDirectory('flutter');
when(windowsProject.managedDirectory).thenReturn(windowsManagedDirectory);
when(windowsProject.vcprojFile).thenReturn(windowsManagedDirectory.parent.childFile('Runner.vcxproj'));
when(windowsProject.solutionFile).thenReturn(windowsManagedDirectory.parent.childFile('Runner.sln'));
when(windowsProject.cmakeFile).thenReturn(windowsManagedDirectory.parent.childFile('CMakeLists.txt'));
when(windowsProject.generatedPluginCmakeFile).thenReturn(windowsManagedDirectory.childFile('generated_plugins.mk'));
when(windowsProject.pluginSymlinkDirectory).thenReturn(windowsManagedDirectory.childDirectory('ephemeral').childDirectory('.plugin_symlinks'));
when(windowsProject.generatedPluginPropertySheetFile).thenReturn(windowsManagedDirectory.childFile('GeneratedPlugins.props'));
when(windowsProject.existsSync()).thenReturn(false);
linuxProject = MockLinuxProject();
when(flutterProject.linux).thenReturn(linuxProject);
......@@ -323,44 +322,6 @@ dependencies:
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', () {
testUsingContext('Refreshing the plugin list is a no-op when the plugins list stays empty', () async {
await refreshPluginsList(flutterProject);
......@@ -1051,8 +1012,6 @@ flutter:
when(featureFlags.isWindowsEnabled).thenReturn(true);
when(flutterProject.isModule).thenReturn(false);
configureDummyPackageAsPlugin();
createDummyWindowsSolutionFile();
createDummyPluginWindowsProjectFile();
await injectPlugins(flutterProject, checkProjects: true);
......@@ -1083,9 +1042,6 @@ flutter:
dartPluginClass: SomePlugin
''');
createDummyWindowsSolutionFile();
createDummyPluginWindowsProjectFile();
await injectPlugins(flutterProject, checkProjects: true);
final File registrantImpl = windowsProject.managedDirectory.childFile('generated_plugin_registrant.cc');
......@@ -1114,9 +1070,6 @@ flutter:
dartPluginClass: SomePlugin
''');
createDummyWindowsSolutionFile();
createDummyPluginWindowsProjectFile();
await injectPlugins(flutterProject, checkProjects: true);
final File registrantImpl = windowsProject.managedDirectory.childFile('generated_plugin_registrant.cc');
......@@ -1130,39 +1083,22 @@ flutter:
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(featureFlags.isWindowsEnabled).thenReturn(true);
when(flutterProject.isModule).thenReturn(false);
configureDummyPackageAsPlugin();
createDummyWindowsSolutionFile();
createDummyPluginWindowsProjectFile();
await injectPlugins(flutterProject, checkProjects: true);
final File properties = windowsProject.generatedPluginPropertySheetFile;
final String includePath = fs.path.join('flutter', 'ephemeral', '.plugin_symlinks', 'apackage', 'windows');
final File pluginMakefile = windowsProject.generatedPluginCmakeFile;
expect(properties.existsSync(), isTrue);
expect(properties.readAsStringSync(), contains('apackage_plugin.lib'));
expect(properties.readAsStringSync(), contains('>$includePath;'));
}, overrides: <Type, Generator>{
FileSystem: () => fs,
ProcessManager: () => FakeProcessManager.any(),
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'));
expect(pluginMakefile.existsSync(), isTrue);
final String contents = pluginMakefile.readAsStringSync();
expect(contents, contains('apackage'));
expect(contents, contains('target_link_libraries(\${BINARY_NAME} PRIVATE \${plugin}_plugin)'));
expect(contents, contains('list(APPEND PLUGIN_BUNDLED_LIBRARIES \$<TARGET_FILE:\${plugin}_plugin>)'));
expect(contents, contains('list(APPEND PLUGIN_BUNDLED_LIBRARIES \${\${plugin}_bundled_libraries})'));
}, overrides: <Type, Generator>{
FileSystem: () => fs,
ProcessManager: () => FakeProcessManager.any(),
......
......@@ -223,7 +223,7 @@ void main() {
});
testUsingContext('injects plugins for Linux', () async {
final FlutterProject project = await someProject();
project.linux.managedDirectory.createSync(recursive: true);
project.linux.cmakeFile.createSync(recursive: true);
await project.ensureReadyForPlatformSpecificTooling();
expectExists(project.linux.managedDirectory.childFile('generated_plugin_registrant.h'));
expectExists(project.linux.managedDirectory.childFile('generated_plugin_registrant.cc'));
......@@ -238,21 +238,7 @@ void main() {
});
testUsingContext('injects plugins for Windows', () async {
final FlutterProject project = await someProject();
project.windows.managedDirectory.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''');
project.windows.cmakeFile.createSync(recursive: true);
await project.ensureReadyForPlatformSpecificTooling();
expectExists(project.windows.managedDirectory.childFile('generated_plugin_registrant.h'));
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());
});
});
}
......@@ -17,7 +17,7 @@ import '../../src/mocks.dart';
const String programFilesPath = r'C:\Program Files (x86)';
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';
final Platform windowsPlatform = FakePlatform(
......@@ -71,6 +71,7 @@ const Map<String, dynamic> _missingStatusResponse = <String, dynamic>{
const List<String> _requirements = <String>[
'Microsoft.VisualStudio.Workload.NativeDesktop',
'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
......@@ -85,7 +86,7 @@ void setMockVswhereResponse(
String responseOverride,
]) {
fileSystem.file(vswherePath).createSync(recursive: true);
fileSystem.file(vcvarsPath).createSync(recursive: true);
fileSystem.file(cmakePath).createSync(recursive: true);
final String finalResponse = responseOverride
?? json.encode(<Map<String, dynamic>>[response]);
final List<String> requirementArguments = requiredComponents == null
......@@ -249,7 +250,7 @@ void main() {
expect(visualStudio.isInstalled, false);
});
testWithoutContext('vcvarsPath returns null when vswhere is missing', () {
testWithoutContext('cmakePath returns null when vswhere is missing', () {
final MockProcessManager mockProcessManager = MockProcessManager();
when(mockProcessManager.runSync(
any,
......@@ -265,7 +266,7 @@ void main() {
processManager: mockProcessManager,
);
expect(visualStudio.vcvarsPath, isNull);
expect(visualStudio.cmakePath, isNull);
});
testWithoutContext(
......@@ -587,7 +588,7 @@ void main() {
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 VisualStudio visualStudio = fixture.visualStudio;
......@@ -607,10 +608,10 @@ void main() {
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 VisualStudio visualStudio = fixture.visualStudio;
......@@ -627,7 +628,7 @@ void main() {
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', () {
......@@ -676,7 +677,7 @@ void main() {
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 VisualStudio visualStudio = fixture.visualStudio;
......@@ -696,7 +697,7 @@ void main() {
fixture.processManager,
);
expect(visualStudio.vcvarsPath, isNull);
expect(visualStudio.cmakePath, isNull);
});
testWithoutContext('Everything returns good values when VS is present with all components', () {
......@@ -722,7 +723,7 @@ void main() {
expect(visualStudio.isInstalled, true);
expect(visualStudio.isAtLeastMinimumVersion, 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', () {
......
......@@ -71,7 +71,7 @@ void main() {
globals.fs.file('pubspec.yaml').createSync();
globals.fs.file('.packages').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();
expect(WindowsDevice().isSupportedForProject(flutterProject), true);
......@@ -91,7 +91,7 @@ void main() {
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('.packages').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