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

5
import 'package:process/process.dart';
6
import 'package:unified_analytics/unified_analytics.dart';
7

8
import '../artifacts.dart';
9 10
import '../base/common.dart';
import '../base/file_system.dart';
11
import '../base/logger.dart';
12
import '../base/project_migrator.dart';
13
import '../base/utils.dart';
14
import '../build_info.dart';
15
import '../build_system/build_system.dart';
16
import '../cache.dart';
17
import '../flutter_plugins.dart';
18
import '../globals.dart' as globals;
19 20
import '../platform_plugins.dart';
import '../plugins.dart';
21
import '../project.dart';
22 23
import '../reporting/reporting.dart';
import '../version.dart';
24
import 'compiler_config.dart';
25
import 'file_generators/flutter_service_worker_js.dart';
26
import 'migrations/scrub_generated_plugin_registrant.dart';
27

28
export 'compiler_config.dart';
29

30 31 32 33 34 35 36 37 38
/// Whether the application has web plugins.
const String kHasWebPlugins = 'HasWebPlugins';

/// Base href to set in index.html in flutter build command
const String kBaseHref = 'baseHref';

/// The caching strategy to use for service worker generation.
const String kServiceWorkerStrategy = 'ServiceWorkerStrategy';

39 40 41
class WebBuilder {
  WebBuilder({
    required Logger logger,
42
    required ProcessManager processManager,
43 44
    required BuildSystem buildSystem,
    required Usage usage,
45
    required Analytics analytics,
46 47 48
    required FlutterVersion flutterVersion,
    required FileSystem fileSystem,
  })  : _logger = logger,
49
        _processManager = processManager,
50 51
        _buildSystem = buildSystem,
        _flutterUsage = usage,
52
        _analytics = analytics,
53 54
        _flutterVersion = flutterVersion,
        _fileSystem = fileSystem;
55

56
  final Logger _logger;
57
  final ProcessManager _processManager;
58 59
  final BuildSystem _buildSystem;
  final Usage _flutterUsage;
60
  final Analytics _analytics;
61 62
  final FlutterVersion _flutterVersion;
  final FileSystem _fileSystem;
63

64 65 66 67
  Future<void> buildWeb(
    FlutterProject flutterProject,
    String target,
    BuildInfo buildInfo,
68
    ServiceWorkerStrategy serviceWorkerStrategy, {
69
    required List<WebCompilerConfig> compilerConfigs,
70 71 72 73 74 75
    String? baseHref,
    String? outputDirectoryPath,
  }) async {
    final bool hasWebPlugins =
        (await findPlugins(flutterProject)).any((Plugin p) => p.platforms.containsKey(WebPlugin.kConfigKey));
    final Directory outputDirectory = outputDirectoryPath == null
76
        ? _fileSystem.directory(getWebBuildDirectory())
77 78
        : _fileSystem.directory(outputDirectoryPath);
    outputDirectory.createSync(recursive: true);
79

80 81 82 83 84 85 86 87 88 89 90 91
    // The migrators to apply to a Web project.
    final List<ProjectMigrator> migrators = <ProjectMigrator>[
      ScrubGeneratedPluginRegistrant(flutterProject.web, _logger),
    ];

    final ProjectMigration migration = ProjectMigration(migrators);
    migration.run();

    final Status status = _logger.startProgress('Compiling $target for the Web...');
    final Stopwatch sw = Stopwatch()..start();
    try {
      final BuildResult result = await _buildSystem.build(
92
          globals.buildTargets.webServiceWorker(_fileSystem, compilerConfigs),
93 94 95 96 97 98 99 100
          Environment(
            projectDir: _fileSystem.currentDirectory,
            outputDir: outputDirectory,
            buildDir: flutterProject.directory.childDirectory('.dart_tool').childDirectory('flutter_build'),
            defines: <String, String>{
              kTargetFile: target,
              kHasWebPlugins: hasWebPlugins.toString(),
              if (baseHref != null) kBaseHref: baseHref,
101
              kServiceWorkerStrategy: serviceWorkerStrategy.cliName,
102 103 104 105 106
              ...buildInfo.toBuildSystemEnvironment(),
            },
            artifacts: globals.artifacts!,
            fileSystem: _fileSystem,
            logger: _logger,
107
            processManager: _processManager,
108 109
            platform: globals.platform,
            usage: _flutterUsage,
110
            analytics: _analytics,
111 112 113 114 115 116 117 118 119 120 121 122 123 124 125
            cacheDir: globals.cache.getRoot(),
            engineVersion: globals.artifacts!.isLocalEngine ? null : _flutterVersion.engineRevision,
            flutterRootDir: _fileSystem.directory(Cache.flutterRoot),
            // Web uses a different Dart plugin registry.
            // https://github.com/flutter/flutter/issues/80406
            generateDartPluginRegistry: false,
          ));
      if (!result.success) {
        for (final ExceptionMeasurement measurement in result.exceptions.values) {
          _logger.printError(
            'Target ${measurement.target} failed: ${measurement.exception}',
            stackTrace: measurement.fatal ? measurement.stackTrace : null,
          );
        }
        throwToolExit('Failed to compile application for the Web.');
126
      }
127 128 129 130
    } on Exception catch (err) {
      throwToolExit(err.toString());
    } finally {
      status.stop();
131
    }
132 133

    final String buildSettingsString = _buildEventAnalyticsSettings(
134
      configs: compilerConfigs,
135 136
    );

137 138 139
    BuildEvent(
      'web-compile',
      type: 'web',
140
      settings: buildSettingsString,
141 142
      flutterUsage: _flutterUsage,
    ).send();
143 144 145 146 147
    _analytics.send(Event.flutterBuildInfo(
      label: 'web-compile',
      buildType: 'web',
      settings: buildSettingsString,
    ));
148

149
    final Duration elapsedDuration = sw.elapsed;
150
    final String variableName = compilerConfigs.length > 1 ? 'dual-compile' : 'dart2js';
151 152
    _flutterUsage.sendTiming(
      'build',
153
      variableName,
154
      elapsedDuration,
155
    );
156 157
    _analytics.send(Event.timing(
      workflow: 'build',
158
      variableName: variableName,
159 160
      elapsedMilliseconds: elapsedDuration.inMilliseconds,
    ));
161
  }
162
}
163

164
/// Web rendering backend mode.
165
enum WebRendererMode implements CliEnum {
166
  /// Auto detects which rendering backend to use.
167 168
  auto,

169 170
  /// Always uses canvaskit.
  canvaskit,
171

172 173
  /// Always uses html.
  html,
174

175
  /// Always use skwasm.
176 177
  skwasm;

178 179 180 181
  @override
  String get cliName => snakeCase(name, '-');

  @override
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
  String get helpText => switch (this) {
        auto =>
          'Use the HTML renderer on mobile devices, and CanvasKit on desktop devices.',
        canvaskit =>
          'Always use the CanvasKit renderer. This renderer uses WebGL and WebAssembly to render graphics.',
        html =>
          'Always use the HTML renderer. This renderer uses a combination of HTML, CSS, SVG, 2D Canvas, and WebGL.',
        skwasm => 'Always use the experimental skwasm renderer.'
      };

  Iterable<String> get dartDefines => switch (this) {
        WebRendererMode.auto => <String>[
            'FLUTTER_WEB_AUTO_DETECT=true',
          ],
        WebRendererMode.canvaskit => <String>[
            'FLUTTER_WEB_AUTO_DETECT=false',
            'FLUTTER_WEB_USE_SKIA=true',
          ],
        WebRendererMode.html => <String>[
            'FLUTTER_WEB_AUTO_DETECT=false',
            'FLUTTER_WEB_USE_SKIA=false',
          ],
        WebRendererMode.skwasm => <String>[
            'FLUTTER_WEB_AUTO_DETECT=false',
            'FLUTTER_WEB_USE_SKIA=false',
            'FLUTTER_WEB_USE_SKWASM=true',
          ]
      };
210 211 212 213 214 215 216 217 218 219

  List<String> updateDartDefines(List<String> inputDefines) {
    final Set<String> dartDefinesSet = inputDefines.toSet();
    if (!inputDefines.any((String d) => d.startsWith('FLUTTER_WEB_AUTO_DETECT='))
        && inputDefines.any((String d) => d.startsWith('FLUTTER_WEB_USE_SKIA='))) {
      dartDefinesSet.removeWhere((String d) => d.startsWith('FLUTTER_WEB_USE_SKIA='));
    }
    dartDefinesSet.addAll(dartDefines);
    return dartDefinesSet.toList();
  }
220 221 222
}

/// The correct precompiled artifact to use for each build and render mode.
223
const Map<WebRendererMode, Map<NullSafetyMode, HostArtifact>> kDartSdkJsArtifactMap = <WebRendererMode, Map<NullSafetyMode, HostArtifact>>{
224
  WebRendererMode.auto: <NullSafetyMode, HostArtifact> {
225 226 227 228 229 230 231 232 233 234 235
    NullSafetyMode.sound: HostArtifact.webPrecompiledCanvaskitAndHtmlSoundSdk,
    NullSafetyMode.unsound: HostArtifact.webPrecompiledCanvaskitAndHtmlSdk,
  },
  WebRendererMode.canvaskit: <NullSafetyMode, HostArtifact> {
    NullSafetyMode.sound: HostArtifact.webPrecompiledCanvaskitSoundSdk,
    NullSafetyMode.unsound: HostArtifact.webPrecompiledCanvaskitSdk,
  },
  WebRendererMode.html: <NullSafetyMode, HostArtifact> {
    NullSafetyMode.sound: HostArtifact.webPrecompiledSoundSdk,
    NullSafetyMode.unsound: HostArtifact.webPrecompiledSdk,
  },
236 237 238
};

/// The correct source map artifact to use for each build and render mode.
239
const Map<WebRendererMode, Map<NullSafetyMode, HostArtifact>> kDartSdkJsMapArtifactMap = <WebRendererMode, Map<NullSafetyMode, HostArtifact>>{
240
  WebRendererMode.auto: <NullSafetyMode, HostArtifact> {
241 242 243 244 245 246 247 248 249 250 251
    NullSafetyMode.sound: HostArtifact.webPrecompiledCanvaskitAndHtmlSoundSdkSourcemaps,
    NullSafetyMode.unsound: HostArtifact.webPrecompiledCanvaskitAndHtmlSdkSourcemaps,
  },
  WebRendererMode.canvaskit: <NullSafetyMode, HostArtifact> {
    NullSafetyMode.sound: HostArtifact.webPrecompiledCanvaskitSoundSdkSourcemaps,
    NullSafetyMode.unsound: HostArtifact.webPrecompiledCanvaskitSdkSourcemaps,
  },
  WebRendererMode.html: <NullSafetyMode, HostArtifact> {
    NullSafetyMode.sound: HostArtifact.webPrecompiledSoundSdkSourcemaps,
    NullSafetyMode.unsound: HostArtifact.webPrecompiledSdkSourcemaps,
  },
252
};
253

254
String _buildEventAnalyticsSettings({
255
  required List<WebCompilerConfig> configs,
256
}) {
257 258 259 260 261 262 263 264 265 266
  final Map<String, Object> values = <String, Object>{};
  final List<String> renderers = <String>[];
  final List<String> targets = <String>[];
  for (final WebCompilerConfig config in configs) {
    values.addAll(config.buildEventAnalyticsValues);
    renderers.add(config.renderer.name);
    targets.add(config.compileTarget.name);
  }
  values['web-renderer'] = renderers.join(',');
  values['web-target'] = targets.join(',');
267 268 269 270 271 272 273 274

  final List<String> sortedList = values.entries
      .map((MapEntry<String, Object> e) => '${e.key}: ${e.value};')
      .toList()
    ..sort();

  return sortedList.join(' ');
}