web_compilation_delegate.dart 9.5 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:build_daemon/client.dart';
6
import 'package:build_daemon/constants.dart' as daemon;
7
import 'package:build_daemon/data/build_status.dart';
8 9 10
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
11

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

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

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

109 110
class WebTestTargetManifest {
  WebTestTargetManifest(this.buildFilters);
111

112
  WebTestTargetManifest.all() : buildFilters = null;
113

114
  final List<String> buildFilters;
115

116 117
  bool get hasBuildFilters => buildFilters != null && buildFilters.isNotEmpty;
}
118

119 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
/// 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.'
      );
155
    }
156
    return null;
157 158
  }

159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175
  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);
176
        }
177
      }));
178 179 180
    }
  }

181 182 183 184 185 186 187
  Future<BuildDaemonClient> _connectClient(
    String workingDirectory, {
    bool release,
    bool profile,
    bool hasPlugins,
    bool initializePlatform,
    WebTestTargetManifest testTargets,
188 189 190 191
  }) async {
    // The build script is stored in an auxiliary package to reduce
    // dependencies of the main tool.
    final String buildScriptPackages = globals.fs.path.join(
192 193
      Cache.flutterRoot,
      'packages',
194
      '_flutter_web_build_script',
195 196 197 198 199
      '.packages',
    );
    final String buildScript = globals.fs.path.join(
      Cache.flutterRoot,
      'packages',
200
      '_flutter_web_build_script',
201 202 203
      'lib',
      'build_script.dart',
    );
204 205 206 207 208 209 210 211
    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,
212
        generateSyntheticPackage: false,
213 214
      );
    }
215
    final String flutterWebSdk = globals.artifacts.getArtifactPath(Artifact.flutterWebSdk);
216

217 218 219 220 221
    // 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),
222
      '--disable-dart-dev',
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
    );
  }
}