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
diff --git a/packages/flutter_tools/lib/src/linux/cmake.dart b/packages/flutter_tools/lib/src/cmake.dart
similarity index 63%
rename from packages/flutter_tools/lib/src/linux/cmake.dart
rename to packages/flutter_tools/lib/src/cmake.dart
index 8a50066..e57b974 100644
--- a/packages/flutter_tools/lib/src/linux/cmake.dart
+++ b/packages/flutter_tools/lib/src/cmake.dart
@@ -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 @@
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(')');
diff --git a/packages/flutter_tools/lib/src/linux/application_package.dart b/packages/flutter_tools/lib/src/linux/application_package.dart
index d1a579f..acc80f2 100644
--- a/packages/flutter_tools/lib/src/linux/application_package.dart
+++ b/packages/flutter_tools/lib/src/linux/application_package.dart
@@ -7,9 +7,9 @@
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 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 @@
}
@override
- String get name => project.project.manifest.appName;
+ String get name => project.parent.manifest.appName;
}
diff --git a/packages/flutter_tools/lib/src/linux/build_linux.dart b/packages/flutter_tools/lib/src/linux/build_linux.dart
index 2ff8690..fbbbf9e 100644
--- a/packages/flutter_tools/lib/src/linux/build_linux.dart
+++ b/packages/flutter_tools/lib/src/linux/build_linux.dart
@@ -10,10 +10,10 @@
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 @@
}
writeGeneratedCmakeConfig(Cache.flutterRoot, linuxProject, environmentConfig);
- createPluginSymlinks(linuxProject.project);
+ createPluginSymlinks(linuxProject.parent);
final Status status = globals.logger.startProgress(
'Building Linux application...',
diff --git a/packages/flutter_tools/lib/src/plugins.dart b/packages/flutter_tools/lib/src/plugins.dart
index 97e3406..d8b32d9 100644
--- a/packages/flutter_tools/lib/src/plugins.dart
+++ b/packages/flutter_tools/lib/src/plugins.dart
@@ -6,6 +6,7 @@
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 '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 @@
#include "generated_plugin_registrant.h"
{{#plugins}}
-#include <{{filename}}.h>
+#include <{{name}}/{{filename}}.h>
{{/plugins}}
void RegisterPlugins(flutter::PluginRegistry* registry) {
@@ -848,7 +847,7 @@
}
''';
-const String _linuxPluginCmakefileTemplate = r'''
+const String _pluginCmakefileTemplate = r'''
#
# Generated file, do not edit.
#
@@ -862,7 +861,7 @@
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 @@
// 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 @@
),
};
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> _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 @@
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> _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 @@
}
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())) {
diff --git a/packages/flutter_tools/lib/src/project.dart b/packages/flutter_tools/lib/src/project.dart
index 2fe310c..c3b566e 100644
--- a/packages/flutter_tools/lib/src/project.dart
+++ b/packages/flutter_tools/lib/src/project.dart
@@ -329,6 +329,27 @@
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 @@
}
/// 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();
- Directory get _editableDirectory => project.directory.childDirectory('windows');
+ @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 => 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 @@
/// 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 @@
}
/// 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 @@
@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.
diff --git a/packages/flutter_tools/lib/src/windows/application_package.dart b/packages/flutter_tools/lib/src/windows/application_package.dart
index bd93c80..8051b06 100644
--- a/packages/flutter_tools/lib/src/windows/application_package.dart
+++ b/packages/flutter_tools/lib/src/windows/application_package.dart
@@ -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 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;
}
diff --git a/packages/flutter_tools/lib/src/windows/build_windows.dart b/packages/flutter_tools/lib/src/windows/build_windows.dart
index 9a3f813..31a9967 100644
--- a/packages/flutter_tools/lib/src/windows/build_windows.dart
+++ b/packages/flutter_tools/lib/src/windows/build_windows.dart
@@ -10,18 +10,23 @@
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 @@
}
// 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 @@
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 {
- // 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,
+ '-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 {
+ result = await processUtils.stream(
+ <String>[
+ 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 @@
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 @@
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
diff --git a/packages/flutter_tools/lib/src/windows/property_sheet.dart b/packages/flutter_tools/lib/src/windows/property_sheet.dart
deleted file mode 100644
index e088485..0000000
--- a/packages/flutter_tools/lib/src/windows/property_sheet.dart
+++ /dev/null
@@ -1,121 +0,0 @@
-// 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');
- });
- });
- }
- }
-}
diff --git a/packages/flutter_tools/lib/src/windows/visual_studio.dart b/packages/flutter_tools/lib/src/windows/visual_studio.dart
index ac2d9fa..6187fee 100644
--- a/packages/flutter_tools/lib/src/windows/visual_studio.dart
+++ b/packages/flutter_tools/lib/src/windows/visual_studio.dart
@@ -147,20 +147,24 @@
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 @@
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',
};
}
diff --git a/packages/flutter_tools/lib/src/windows/visual_studio_project.dart b/packages/flutter_tools/lib/src/windows/visual_studio_project.dart
deleted file mode 100644
index 47a4f87..0000000
--- a/packages/flutter_tools/lib/src/windows/visual_studio_project.dart
+++ /dev/null
@@ -1,80 +0,0 @@
-// 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);
- }
- }
-}
diff --git a/packages/flutter_tools/lib/src/windows/visual_studio_solution_utils.dart b/packages/flutter_tools/lib/src/windows/visual_studio_solution_utils.dart
deleted file mode 100644
index f5a40fe..0000000
--- a/packages/flutter_tools/lib/src/windows/visual_studio_solution_utils.dart
+++ /dev/null
@@ -1,345 +0,0 @@
-// 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');
- }
-}