web_compilation_delegate.dart 9.45 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 6
import 'dart:async';

7
import 'package:build_daemon/client.dart';
8
import 'package:build_daemon/constants.dart' as daemon;
9
import 'package:build_daemon/data/build_status.dart';
10 11 12
import 'package:build_daemon/data/build_target.dart';
import 'package:build_daemon/data/server_log.dart';
import 'package:path/path.dart' as path; // ignore: package_path_import
13

14 15
import '../artifacts.dart';
import '../base/common.dart';
16
import '../base/file_system.dart';
17
import '../build_info.dart';
18
import '../cache.dart';
19
import '../dart/pub.dart';
20
import '../globals.dart' as globals;
21 22 23
import '../platform_plugins.dart';
import '../plugins.dart';
import '../project.dart';
24 25 26 27 28 29 30
import '../web/compile.dart';

/// A build_runner specific implementation of the [WebCompilationProxy].
class BuildRunnerWebCompilationProxy extends WebCompilationProxy {
  BuildRunnerWebCompilationProxy();

  @override
31
  Future<bool> initialize({
32
    Directory projectDirectory,
33
    String testOutputDir,
34
    List<String> testFiles,
35
    BuildMode mode,
36 37
    String projectName,
    bool initializePlatform,
38
  }) async {
39
    // Create the .dart_tool directory if it doesn't exist.
40
    projectDirectory
41
      .childDirectory('.dart_tool')
42
      .createSync();
43 44
    final FlutterProject flutterProject = FlutterProject.fromDirectory(projectDirectory);
    final bool hasWebPlugins = findPlugins(flutterProject)
45 46
      .any((Plugin p) => p.platforms.containsKey(WebPlugin.kConfigKey));
    final BuildDaemonClient client = await const BuildDaemonCreator().startBuildDaemon(
47
      projectDirectory.path,
48 49
      release: mode == BuildMode.release,
      profile: mode == BuildMode.profile,
50 51
      hasPlugins: hasWebPlugins,
      initializePlatform: initializePlatform,
52 53 54 55 56 57 58 59
      testTargets: WebTestTargetManifest(
        testFiles
          .map<String>((String absolutePath) {
            final String relativePath = path.relative(absolutePath, from: projectDirectory.path);
            return '${path.withoutExtension(relativePath)}.*';
          })
          .toList(),
      ),
60 61 62
    );
    client.startBuild();
    bool success = true;
63
    await for (final BuildResults results in client.buildResults) {
64 65
      final BuildResult result = results.results.firstWhere((BuildResult result) {
        return result.target == 'web';
66 67 68
      }, orElse: () {
        // Assume build failed if we lack any results.
        return DefaultBuildResult((DefaultBuildResultBuilder b) => b.status == BuildStatus.failed);
69 70 71 72 73 74 75
      });
      if (result.status == BuildStatus.failed) {
        success = false;
        break;
      }
      if (result.status == BuildStatus.succeeded) {
        break;
76
      }
77
    }
78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94
    if (!success || testOutputDir == null) {
      return success;
    }
    final Directory rootDirectory = projectDirectory
      .childDirectory('.dart_tool')
      .childDirectory('build')
      .childDirectory('flutter_web');

    final Iterable<Directory> childDirectories = rootDirectory
      .listSync()
      .whereType<Directory>();
    for (final Directory childDirectory in childDirectories) {
      final String path = globals.fs.path.join(
        testOutputDir,
        'packages',
        globals.fs.path.basename(childDirectory.path),
      );
95
      globals.fsUtils.copyDirectorySync(
96 97
        childDirectory.childDirectory('lib'),
        globals.fs.directory(path),
98
      );
99
    }
100 101 102 103 104 105 106
    final Directory outputDirectory = rootDirectory
      .childDirectory(projectName)
      .childDirectory('test');
    globals.fsUtils.copyDirectorySync(
      outputDirectory,
      globals.fs.directory(globals.fs.path.join(testOutputDir)),
    );
107
    return success;
108
  }
109
}
110

111 112
class WebTestTargetManifest {
  WebTestTargetManifest(this.buildFilters);
113

114
  WebTestTargetManifest.all() : buildFilters = null;
115

116
  final List<String> buildFilters;
117

118 119
  bool get hasBuildFilters => buildFilters != null && buildFilters.isNotEmpty;
}
120

121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156
/// A testable interface for starting a build daemon.
class BuildDaemonCreator {
  const BuildDaemonCreator();

  // TODO(jonahwilliams): find a way to get build checks working for flutter for web.
  static const String _ignoredLine1 = 'Warning: Interpreting this as package URI';
  static const String _ignoredLine2 = 'build_script.dart was not found in the asset graph, incremental builds will not work';
  static const String _ignoredLine3 = 'have your dependencies specified fully in your pubspec.yaml';

  /// Start a build daemon and register the web targets.
  ///
  /// [initializePlatform] controls whether we should invoke [webOnlyInitializePlatform].
  Future<BuildDaemonClient> startBuildDaemon(String workingDirectory, {
    bool release = false,
    bool profile = false,
    bool hasPlugins = false,
    bool initializePlatform = true,
    WebTestTargetManifest testTargets,
  }) async {
    try {
      final BuildDaemonClient client = await _connectClient(
        workingDirectory,
        release: release,
        profile: profile,
        hasPlugins: hasPlugins,
        initializePlatform: initializePlatform,
        testTargets: testTargets,
      );
      _registerBuildTargets(client, testTargets);
      return client;
    } on OptionsSkew {
      throwToolExit(
        'Incompatible options with current running build daemon.\n\n'
        'Please stop other flutter_tool instances running in this directory '
        'before starting a new instance with these options.'
      );
157
    }
158
    return null;
159 160
  }

161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177
  void _registerBuildTargets(
    BuildDaemonClient client,
    WebTestTargetManifest testTargets,
  ) {
    final OutputLocation outputLocation = OutputLocation((OutputLocationBuilder b) => b
      ..output = ''
      ..useSymlinks = true
      ..hoist = false);
    client.registerBuildTarget(DefaultBuildTarget((DefaultBuildTargetBuilder b) => b
      ..target = 'web'
      ..outputLocation = outputLocation?.toBuilder()));
    if (testTargets != null) {
      client.registerBuildTarget(DefaultBuildTarget((DefaultBuildTargetBuilder b) {
        b.target = 'test';
        b.outputLocation = outputLocation?.toBuilder();
        if (testTargets.hasBuildFilters) {
          b.buildFilters.addAll(testTargets.buildFilters);
178
        }
179
      }));
180 181 182
    }
  }

183 184 185 186 187 188 189
  Future<BuildDaemonClient> _connectClient(
    String workingDirectory, {
    bool release,
    bool profile,
    bool hasPlugins,
    bool initializePlatform,
    WebTestTargetManifest testTargets,
190 191 192 193
  }) async {
    // The build script is stored in an auxiliary package to reduce
    // dependencies of the main tool.
    final String buildScriptPackages = globals.fs.path.join(
194 195
      Cache.flutterRoot,
      'packages',
196
      '_flutter_web_build_script',
197 198 199 200 201
      '.packages',
    );
    final String buildScript = globals.fs.path.join(
      Cache.flutterRoot,
      'packages',
202
      '_flutter_web_build_script',
203 204 205
      'lib',
      'build_script.dart',
    );
206 207 208 209 210 211 212 213 214 215
    if (!globals.fs.isFileSync(buildScript)) {
      throwToolExit('Expected a file $buildScript to exist in the Flutter SDK.');
    }
    // If we're missing the .packages file, perform a pub get.
    if (!globals.fs.isFileSync(buildScriptPackages)) {
      await pub.get(
        context: PubContext.pubGet,
        directory: globals.fs.file(buildScriptPackages).parent.path,
      );
    }
216
    final String flutterWebSdk = globals.artifacts.getArtifactPath(Artifact.flutterWebSdk);
217

218 219 220 221 222
    // On Windows we need to call the snapshot directly otherwise
    // the process will start in a disjoint cmd without access to
    // STDIO.
    final List<String> args = <String>[
      globals.artifacts.getArtifactPath(Artifact.engineDartBinary),
223
      '--packages=$buildScriptPackages',
224 225 226 227 228 229 230 231 232 233 234 235 236 237 238
      buildScript,
      'daemon',
      '--skip-build-script-check',
      '--define', 'flutter_tools:ddc=flutterWebSdk=$flutterWebSdk',
      '--define', 'flutter_tools:entrypoint=flutterWebSdk=$flutterWebSdk',
      '--define', 'flutter_tools:entrypoint=release=$release',
      '--define', 'flutter_tools:entrypoint=profile=$profile',
      '--define', 'flutter_tools:shell=flutterWebSdk=$flutterWebSdk',
      '--define', 'flutter_tools:shell=hasPlugins=$hasPlugins',
      '--define', 'flutter_tools:shell=initializePlatform=$initializePlatform',
      // The following will cause build runner to only build tests that were requested.
      if (testTargets != null && testTargets.hasBuildFilters)
        for (final String buildFilter in testTargets.buildFilters)
          '--build-filter=$buildFilter',
    ];
239

240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269
    return BuildDaemonClient.connect(
      workingDirectory,
      args,
      logHandler: (ServerLog serverLog) {
        switch (serverLog.level) {
          case Level.SEVERE:
          case Level.SHOUT:
            // Ignore certain non-actionable messages on startup.
            if (serverLog.message.contains(_ignoredLine1) ||
                serverLog.message.contains(_ignoredLine2) ||
                serverLog.message.contains(_ignoredLine3)) {
              return;
            }
            globals.printError(serverLog.message);
            if (serverLog.error != null) {
              globals.printError(serverLog.error);
            }
            if (serverLog.stackTrace != null) {
              globals.printTrace(serverLog.stackTrace);
            }
            break;
          default:
            if (serverLog.message.contains('Skipping compiling')) {
              globals.printError(serverLog.message);
            } else {
              globals.printTrace(serverLog.message);
            }
        }
      },
      buildMode: daemon.BuildMode.Manual,
270 271 272
    );
  }
}