build_windows.dart 12.2 KB
Newer Older
Ian Hickson's avatar
Ian Hickson committed
1
// Copyright 2014 The Flutter Authors. All rights reserved.
2
// Use of this source code is governed by a BSD-style license that can be
Ian Hickson's avatar
Ian Hickson committed
3
// found in the LICENSE file.
4

5 6
// @dart = 2.8

7
import '../artifacts.dart';
8
import '../base/analyze_size.dart';
9
import '../base/common.dart';
10
import '../base/file_system.dart';
11
import '../base/logger.dart';
12
import '../base/project_migrator.dart';
13
import '../base/utils.dart';
14 15
import '../build_info.dart';
import '../cache.dart';
16
import '../cmake.dart';
17
import '../convert.dart';
18
import '../flutter_plugins.dart';
19
import '../globals_null_migrated.dart' as globals;
20
import '../migrations/cmake_custom_command_migration.dart';
21
import '../project.dart';
22
import 'install_manifest.dart';
23
import 'visual_studio.dart';
24

25 26 27 28 29
// 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';

30 31 32
/// Update the string when non-backwards compatible changes are made to the UWP template.
const int kCurrentUwpTemplateVersion = 0;

33
/// Builds the Windows project using msbuild.
34 35 36
Future<void> buildWindows(WindowsProject windowsProject, BuildInfo buildInfo, {
  String target,
  VisualStudio visualStudioOverride,
37
  SizeAnalyzer sizeAnalyzer,
38
}) async {
39
  if (!windowsProject.cmakeFile.existsSync()) {
40
    throwToolExit(
41
      'No Windows desktop project configured. See '
42
      'https://flutter.dev/desktop#add-desktop-support-to-an-existing-flutter-app '
43 44 45
      'to learn about adding Windows support to a project.');
  }

46 47 48 49 50 51 52 53 54
  final List<ProjectMigrator> migrators = <ProjectMigrator>[
    CmakeCustomCommandMigration(windowsProject, globals.logger),
  ];

  final ProjectMigration migration = ProjectMigration(migrators);
  if (!migration.run()) {
    throwToolExit('Unable to migrate project files');
  }

55
  // Ensure that necessary ephemeral files are generated and up to date.
56 57
  _writeGeneratedFlutterConfig(windowsProject, buildInfo, target);
  createPluginSymlinks(windowsProject.parent);
58

59 60 61 62 63 64
  final VisualStudio visualStudio = visualStudioOverride ?? VisualStudio(
    fileSystem: globals.fs,
    platform: globals.platform,
    logger: globals.logger,
    processManager: globals.processManager,
  );
65 66
  final String cmakePath = visualStudio.cmakePath;
  if (cmakePath == null) {
67 68
    throwToolExit('Unable to find suitable Visual Studio toolchain. '
        'Please run `flutter doctor` for more details.');
69 70
  }

71 72
  final String buildModeName = getNameForBuildMode(buildInfo.mode ?? BuildMode.release);
  final Directory buildDirectory = globals.fs.directory(getWindowsBuildDirectory());
73
  final Status status = globals.logger.startProgress(
74 75
    'Building Windows application...',
  );
76 77 78 79 80 81
  try {
    await _runCmakeGeneration(cmakePath, buildDirectory, windowsProject.cmakeFile.parent);
    await _runBuild(cmakePath, buildDirectory, buildModeName);
  } finally {
    status.cancel();
  }
82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97
  if (buildInfo.codeSizeDirectory != null && sizeAnalyzer != null) {
    final String arch = getNameForTargetPlatform(TargetPlatform.windows_x64);
    final File codeSizeFile = globals.fs.directory(buildInfo.codeSizeDirectory)
      .childFile('snapshot.$arch.json');
    final File precompilerTrace = globals.fs.directory(buildInfo.codeSizeDirectory)
      .childFile('trace.$arch.json');
    final Map<String, Object> output = await sizeAnalyzer.analyzeAotSnapshot(
      aotSnapshot: codeSizeFile,
      // This analysis is only supported for release builds.
      outputDirectory: globals.fs.directory(
        globals.fs.path.join(getWindowsBuildDirectory(), 'runner', 'Release'),
      ),
      precompilerTrace: precompilerTrace,
      type: 'windows',
    );
    final File outputFile = globals.fsUtils.getUniqueFile(
98 99 100
      globals.fs
        .directory(globals.fsUtils.homeDirPath)
        .childDirectory('.flutter-devtools'), 'windows-code-size-analysis', 'json',
101 102 103 104 105
    )..writeAsStringSync(jsonEncode(output));
    // This message is used as a sentinel in analyze_apk_size_test.dart
    globals.printStatus(
      'A summary of your Windows bundle analysis can be found at: ${outputFile.path}',
    );
106 107 108 109 110 111 112 113

    // DevTools expects a file path relative to the .flutter-devtools/ dir.
    final String relativeAppSizePath = outputFile.path.split('.flutter-devtools/').last.trim();
    globals.printStatus(
      '\nTo analyze your app size in Dart DevTools, run the following command:\n'
      'flutter pub global activate devtools; flutter pub global run devtools '
      '--appSizeBase=$relativeAppSizePath'
    );
114
  }
115 116
}

117 118 119 120 121 122 123
/// Build the Windows UWP project.
///
/// Note that this feature is currently unfinished.
Future<void> buildWindowsUwp(WindowsUwpProject windowsProject, BuildInfo buildInfo, {
  String target,
  VisualStudio visualStudioOverride,
}) async {
124
  final Directory buildDirectory = globals.fs.directory(getWindowsBuildUwpDirectory());
125 126 127 128 129 130 131 132 133 134 135 136
  if (!windowsProject.existsSync()) {
    throwToolExit(
      'No Windows UWP desktop project configured. See '
      'https://flutter.dev/desktop#add-desktop-support-to-an-existing-flutter-app '
      'to learn about adding Windows support to a project.',
    );
  }
  if (windowsProject.projectVersion != kCurrentUwpTemplateVersion) {
    throwToolExit(
      'The Windows UWP project template and build process has changed. In order to build '
      'you must delete the winuwp directory and re-create the project.',
    );
137 138 139 140
  }
   // Ensure that necessary ephemeral files are generated and up to date.
  _writeGeneratedFlutterConfig(windowsProject, buildInfo, target);
  createPluginSymlinks(windowsProject.parent);
141 142 143 144 145 146 147 148
  await createManifest(
    buildDirectory: buildDirectory,
    logger: globals.logger,
    platform: globals.platform,
    project: windowsProject,
    buildInfo: buildInfo,
    fileSystem: globals.fs,
  );
149 150 151 152 153 154 155 156 157 158 159 160 161

  final VisualStudio visualStudio = visualStudioOverride ?? VisualStudio(
    fileSystem: globals.fs,
    platform: globals.platform,
    logger: globals.logger,
    processManager: globals.processManager,
  );
  final String cmakePath = visualStudio.cmakePath;
  if (cmakePath == null) {
    throwToolExit('Unable to find suitable Visual Studio toolchain. '
        'Please run `flutter doctor` for more details.');
  }

162
  final String buildModeName = getNameForBuildMode(buildInfo.mode ?? BuildMode.release);
163
  final Status status = globals.logger.startProgress(
164
    'Building Windows UWP application...',
165 166
  );
  try {
167 168 169
    // The Cmake re-entrant build does not work for UWP, so the flutter build is
    // run in advance.
    await _runFlutterBuild(buildDirectory, buildInfo, target);
170
    await _runCmakeGeneration(cmakePath, buildDirectory, windowsProject.cmakeFile.parent);
171
    await _runBuild(cmakePath, buildDirectory, buildModeName, install: false);
172 173
  } finally {
    status.cancel();
174
  }
175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211
}

const Map<BuildMode, String> _targets = <BuildMode, String>{
  BuildMode.debug: 'debug_bundle_windows_assets_uwp',
  BuildMode.profile: 'profile_bundle_windows_assets_uwp',
  BuildMode.release: 'release_bundle_windows_assets_uwp',
};

Future<void> _runFlutterBuild(Directory buildDirectory, BuildInfo buildInfo, String targetFile) async {
  await buildDirectory.create(recursive: true);
  int result;
  String flutterEngine;
  String localEngine;
  if (globals.artifacts is LocalEngineArtifacts) {
    final LocalEngineArtifacts localEngineArtifacts = globals.artifacts as LocalEngineArtifacts;
    final String engineOutPath = localEngineArtifacts.engineOutPath;
    flutterEngine = globals.fs.path.dirname(globals.fs.path.dirname(engineOutPath));
    localEngine = globals.fs.path.basename(engineOutPath);
  }
  try {
    result = await globals.processUtils.stream(
      <String>[
        globals.fs.path.join(Cache.flutterRoot, 'bin', 'flutter'),
        if (globals.logger.isVerbose)
          '--verbose',
        if (flutterEngine != null) '--local-engine-src-path=$flutterEngine',
        if (localEngine != null) '--local-engine=$localEngine',
        'assemble',
        '--no-version-check',
        '--output=build',
        '-dTargetPlatform=windows-uwp-x64',
        '-dTrackWidgetCreation=${buildInfo.trackWidgetCreation}',
        '-dBuildMode=${getNameForBuildMode(buildInfo.mode)}',
        '-dTargetFile=$targetFile',
        '-dTreeShakeIcons="${buildInfo.treeShakeIcons}"',
        '-dDartObfuscation=${buildInfo.dartObfuscation}',
        if (buildInfo.bundleSkSLPath != null)
212
          '-dBundleSkSLPath=${buildInfo.bundleSkSLPath}',
213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232
        if (buildInfo.codeSizeDirectory != null)
          '-dCodeSizeDirectory=${buildInfo.codeSizeDirectory}',
        if (buildInfo.splitDebugInfoPath != null)
          '-dSplitDebugInfo=${buildInfo.splitDebugInfoPath}',
        if (buildInfo.dartDefines != null && buildInfo.dartDefines.isNotEmpty)
          '--DartDefines=${encodeDartDefines(buildInfo.dartDefines)}',
        if (buildInfo.extraGenSnapshotOptions != null && buildInfo.extraGenSnapshotOptions.isNotEmpty)
          '--ExtraGenSnapshotOptions=${buildInfo.extraGenSnapshotOptions}',
        if (buildInfo.extraFrontEndOptions != null && buildInfo.extraFrontEndOptions.isNotEmpty)
          '--ExtraFrontEndOptions=${buildInfo.extraFrontEndOptions}',
        _targets[buildInfo.mode],
      ],
      trace: true,
    );
  } on ArgumentError {
    throwToolExit("cmake not found. Run 'flutter doctor' for more information.");
  }
  if (result != 0) {
    throwToolExit('Unable to generate build files');
  }
233 234
}

235 236 237 238 239 240
Future<void> _runCmakeGeneration(String cmakePath, Directory buildDir, Directory sourceDir) async {
  final Stopwatch sw = Stopwatch()..start();

  await buildDir.create(recursive: true);
  int result;
  try {
241
    result = await globals.processUtils.stream(
242 243 244 245 246 247 248 249 250 251 252
      <String>[
        cmakePath,
        '-S',
        sourceDir.path,
        '-B',
        buildDir.path,
        '-G',
        _cmakeVisualStudioGeneratorIdentifier,
      ],
      trace: true,
    );
253
  } on ArgumentError {
254 255 256 257 258 259 260 261
    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));
}

262 263 264 265 266 267
Future<void> _runBuild(
  String cmakePath,
  Directory buildDir,
  String buildModeName,
  { bool install = true }
) async {
268 269
  final Stopwatch sw = Stopwatch()..start();

270 271 272 273
  // MSBuild sends all output to stdout, including build errors. This surfaces
  // known error patterns.
  final RegExp errorMatcher = RegExp(r':\s*(?:warning|(?:fatal )?error).*?:');

274 275
  int result;
  try {
276
    result = await globals.processUtils.stream(
277
      <String>[
278 279 280 281 282
        cmakePath,
        '--build',
        buildDir.path,
        '--config',
        toTitleCase(buildModeName),
283 284
        if (install)
          ...<String>['--target', 'INSTALL'],
285 286
        if (globals.logger.isVerbose)
          '--verbose'
287 288 289 290 291 292
      ],
      environment: <String, String>{
        if (globals.logger.isVerbose)
          'VERBOSE_SCRIPT_LOGGING': 'true'
      },
      trace: true,
293
      stdoutErrorMatcher: errorMatcher,
294
    );
295
  } on ArgumentError {
296
    throwToolExit("cmake not found. Run 'flutter doctor' for more information.");
297 298
  }
  if (result != 0) {
299
    throwToolExit('Build process failed.');
300
  }
301
  globals.flutterUsage.sendTiming('build', 'windows-cmake-build', Duration(milliseconds: sw.elapsedMilliseconds));
302
}
303

304 305
/// Writes the generated CMake file with the configuration for the given build.
void _writeGeneratedFlutterConfig(
306 307 308 309
  WindowsProject windowsProject,
  BuildInfo buildInfo,
  String target,
) {
310 311 312
  final Map<String, String> environment = <String, String>{
    'FLUTTER_ROOT': Cache.flutterRoot,
    'FLUTTER_EPHEMERAL_DIR': windowsProject.ephemeralDirectory.path,
313
    'PROJECT_DIR': windowsProject.parent.directory.path,
314 315
    if (target != null)
      'FLUTTER_TARGET': target,
316
    ...buildInfo.toEnvironmentConfig(),
317 318 319 320 321 322 323
  };
  if (globals.artifacts is LocalEngineArtifacts) {
    final LocalEngineArtifacts localEngineArtifacts = globals.artifacts as LocalEngineArtifacts;
    final String engineOutPath = localEngineArtifacts.engineOutPath;
    environment['FLUTTER_ENGINE'] = globals.fs.path.dirname(globals.fs.path.dirname(engineOutPath));
    environment['LOCAL_ENGINE'] = globals.fs.path.basename(engineOutPath);
  }
324
  writeGeneratedCmakeConfig(Cache.flutterRoot, windowsProject, environment);
325
}