Flutter 1.24, attempt 2 (#622)

diff --git a/.gitignore b/.gitignore
index 52f141b..76d99c4 100644
--- a/.gitignore
+++ b/.gitignore
@@ -18,6 +18,7 @@
 
 # compilation artifacts
 artifacts/
+project_templates/
 
 # local configuration
 config.properties
diff --git a/Dockerfile b/Dockerfile
index 115bcff..4ee5e79 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -6,7 +6,7 @@
 # To retrieve this value, please run the following in your closest shell:
 #
 # $ (cd flutter && git rev-parse HEAD)
-ARG FLUTTER_COMMIT=198df796aa80073ef22bdf249e614e2ff33c6895
+ARG FLUTTER_COMMIT=022b333a089afb81c471ec43d1f1f4f26305d876
 
 # We install unzip and remove the apt-index again to keep the
 # docker image diff small.
diff --git a/cloud_run.Dockerfile b/cloud_run.Dockerfile
index 17877fa..168cd62 100644
--- a/cloud_run.Dockerfile
+++ b/cloud_run.Dockerfile
@@ -6,7 +6,7 @@
 # To retrieve this value, please run the following in your closest shell:
 #
 # $ (cd flutter && git rev-parse HEAD)
-ARG FLUTTER_COMMIT=198df796aa80073ef22bdf249e614e2ff33c6895
+ARG FLUTTER_COMMIT=022b333a089afb81c471ec43d1f1f4f26305d876
 
 # We install unzip and remove the apt-index again to keep the
 # docker image diff small.
diff --git a/flutter b/flutter
index 198df79..022b333 160000
--- a/flutter
+++ b/flutter
@@ -1 +1 @@
-Subproject commit 198df796aa80073ef22bdf249e614e2ff33c6895
+Subproject commit 022b333a089afb81c471ec43d1f1f4f26305d876
diff --git a/lib/src/analysis_server.dart b/lib/src/analysis_server.dart
index ba2b9b5..3d2f0d0 100644
--- a/lib/src/analysis_server.dart
+++ b/lib/src/analysis_server.dart
@@ -76,7 +76,7 @@
   }
 
   @override
-  String get _sourceDirPath => flutterWebManager.projectDirectory.path;
+  String get _sourceDirPath => flutterWebManager.flutterTemplateProject.path;
 
   @override
   Future<proto.AnalysisResults> analyze(String source) {
diff --git a/lib/src/analysis_servers.dart b/lib/src/analysis_servers.dart
index 1b89bfb..4895206 100644
--- a/lib/src/analysis_servers.dart
+++ b/lib/src/analysis_servers.dart
@@ -49,9 +49,6 @@
     _flutterWebManager = FlutterWebManager(SdkManager.flutterSdk);
     _flutterAnalysisServer = FlutterAnalysisServerWrapper(_flutterWebManager);
 
-    await _flutterWebManager.warmup();
-    _logger.info('FlutterWebManager warmed up');
-
     await _dartAnalysisServer.init();
     _logger.info('Dart analysis server initialized.');
 
@@ -93,7 +90,6 @@
     _restartingSince = DateTime.now();
 
     return Future.wait(<Future<dynamic>>[
-      _flutterWebManager.dispose(),
       _flutterAnalysisServer.shutdown(),
       _dartAnalysisServer.shutdown(),
     ]);
diff --git a/lib/src/compiler.dart b/lib/src/compiler.dart
index b45fd9a..061a78a 100644
--- a/lib/src/compiler.dart
+++ b/lib/src/compiler.dart
@@ -9,6 +9,7 @@
 import 'dart:io';
 
 import 'package:bazel_worker/driver.dart';
+import 'package:io/io.dart';
 import 'package:logging/logging.dart';
 import 'package:path/path.dart' as path;
 
@@ -43,7 +44,6 @@
   }
 
   Future<CompilationResults> warmup({bool useHtml = false}) async {
-    await _flutterWebManager.warmup();
     return compile(useHtml ? sampleCodeWeb : sampleCode);
   }
 
@@ -65,16 +65,19 @@
     _logger.info('Temp directory created: ${temp.path}');
 
     try {
+      await copyPath(_flutterWebManager.dartTemplateProject.path, temp.path);
+      await Directory(path.join(temp.path, 'lib')).create(recursive: true);
+
       final arguments = <String>[
         '--suppress-hints',
         '--terse',
         if (!returnSourceMap) '--no-source-maps',
-        '--packages=${_flutterWebManager.packagesFilePath}',
+        '--packages=${path.join('.dart_tool', 'package_config.json')}',
         ...['-o', '$kMainDart.js'],
-        kMainDart,
+        path.join('lib', kMainDart),
       ];
 
-      final compileTarget = path.join(temp.path, kMainDart);
+      final compileTarget = path.join(temp.path, 'lib', kMainDart);
       final mainDart = File(compileTarget);
       await mainDart.writeAsString(input);
 
@@ -82,7 +85,7 @@
       final mainSourceMap = File(path.join(temp.path, '$kMainDart.js.map'));
 
       final dart2JSPath = path.join(_sdk.sdkPath, 'bin', 'dart2js');
-      _logger.info('About to exec: $dart2JSPath $arguments');
+      _logger.info('About to exec: $dart2JSPath ${arguments.join(' ')}');
 
       final result = await Process.run(dart2JSPath, arguments,
           workingDirectory: temp.path);
@@ -128,9 +131,17 @@
 
     try {
       final usingFlutter = _flutterWebManager.usesFlutterWeb(imports);
+      if (usingFlutter) {
+        await copyPath(
+            _flutterWebManager.flutterTemplateProject.path, temp.path);
+      } else {
+        await copyPath(_flutterWebManager.dartTemplateProject.path, temp.path);
+      }
 
-      final mainPath = path.join(temp.path, kMainDart);
-      final bootstrapPath = path.join(temp.path, kBootstrapDart);
+      await Directory(path.join(temp.path, 'lib')).create(recursive: true);
+
+      final mainPath = path.join(temp.path, 'lib', kMainDart);
+      final bootstrapPath = path.join(temp.path, 'lib', kBootstrapDart);
       final bootstrapContents =
           usingFlutter ? kBootstrapFlutterCode : kBootstrapDartCode;
 
@@ -148,7 +159,7 @@
         ...['-o', path.join(temp.path, '$kMainDart.js')],
         ...['--module-name', 'dartpad_main'],
         bootstrapPath,
-        '--packages=${_flutterWebManager.packagesFilePath}',
+        '--packages=${path.join(temp.path, '.dart_tool', 'package_config.json')}',
       ];
 
       final mainJs = File(path.join(temp.path, '$kMainDart.js'));
@@ -189,7 +200,6 @@
   }
 
   Future<void> dispose() async {
-    await _flutterWebManager.dispose();
     return _ddcDriver.terminateWorkers();
   }
 }
diff --git a/lib/src/flutter_web.dart b/lib/src/flutter_web.dart
index 8458400..e078983 100644
--- a/lib/src/flutter_web.dart
+++ b/lib/src/flutter_web.dart
@@ -4,67 +4,21 @@
 
 import 'dart:io';
 
-import 'package:logging/logging.dart';
 import 'package:path/path.dart' as path;
 
 import 'sdk_manager.dart';
 
-Logger _logger = Logger('flutter_web');
-
 /// Handle provisioning package:flutter_web and related work.
 class FlutterWebManager {
   final FlutterSdk flutterSdk;
 
-  Directory _projectDirectory;
+  final Directory flutterTemplateProject = Directory(path.join(
+      Directory.current.path, 'project_templates', 'flutter_project'));
 
-  bool _initedFlutterWeb = false;
+  final Directory dartTemplateProject = Directory(
+      path.join(Directory.current.path, 'project_templates', 'dart_project'));
 
-  FlutterWebManager(this.flutterSdk) {
-    _projectDirectory = Directory.systemTemp.createTempSync('dartpad');
-    _init();
-  }
-
-  Future<void> dispose() => _projectDirectory.delete(recursive: true);
-
-  Directory get projectDirectory => _projectDirectory;
-
-  String get packagesFilePath => path.join(projectDirectory.path, '.packages');
-
-  void _init() {
-    // create a pubspec.yaml file
-    final pubspec = createPubspec(true);
-    File(path.join(_projectDirectory.path, 'pubspec.yaml'))
-        .writeAsStringSync(pubspec);
-
-    // create a .packages file
-    final packagesFileContents = '''
-$_samplePackageName:lib/
-''';
-    File(path.join(_projectDirectory.path, '.packages'))
-        .writeAsStringSync(packagesFileContents);
-
-    // and create a lib/ folder for completeness
-    Directory(path.join(_projectDirectory.path, 'lib')).createSync();
-  }
-
-  Future<void> warmup() async {
-    try {
-      if (_initedFlutterWeb) {
-        return;
-      }
-
-      _logger.info('creating flutter web pubspec');
-      final pubspec = createPubspec(true);
-      await File(path.join(_projectDirectory.path, 'pubspec.yaml'))
-          .writeAsString(pubspec);
-
-      await _runPubGet();
-
-      _initedFlutterWeb = true;
-    } catch (e, s) {
-      _logger.warning('Error initializing flutter web', e, s);
-    }
-  }
+  FlutterWebManager(this.flutterSdk);
 
   String get summaryFilePath {
     return path.join('artifacts', 'flutter_web.dill');
@@ -110,81 +64,4 @@
 
     return null;
   }
-
-  Future<void> _runPubGet() async {
-    _logger.info('running flutter pub get (${_projectDirectory.path})');
-
-    final observatoryPort = await _findFreePort();
-
-    // The DART_VM_OPTIONS flag is included here to override the one sent by the
-    // Dart SDK during tests. Without the flag, the Flutter tool will attempt to
-    // spin up its own observatory on the same port as the one already
-    // instantiated by the Dart SDK running the test, causing a hang.
-    //
-    // The value should be an available port number.
-    final result = await Process.start(
-      path.join(flutterSdk.flutterBinPath, 'flutter'),
-      ['pub', 'get'],
-      workingDirectory: _projectDirectory.path,
-      environment: {'DART_VM_OPTIONS': '--enable-vm-service=$observatoryPort'},
-    );
-
-    _logger.info('${result.stdout}'.trim());
-
-    final code = await result.exitCode;
-
-    if (code != 0) {
-      _logger.warning('pub get failed: ${result.exitCode}');
-      _logger.warning(result.stderr);
-
-      throw 'pub get failed: ${result.exitCode}: ${result.stderr}';
-    }
-  }
-
-  Future<int> _findFreePort({bool ipv6 = false}) async {
-    var port = 0;
-    ServerSocket serverSocket;
-    final loopback =
-        ipv6 ? InternetAddress.loopbackIPv6 : InternetAddress.loopbackIPv4;
-
-    try {
-      serverSocket = await ServerSocket.bind(loopback, 0);
-      port = serverSocket.port;
-    } on SocketException catch (e) {
-      // If ipv4 loopback bind fails, try ipv6.
-      if (!ipv6) {
-        return _findFreePort(ipv6: true);
-      }
-      _logger.severe('Could not find free port for `pub get`: $e');
-    } catch (e) {
-      // Failures are signaled by a return value of 0 from this function.
-      _logger.severe('Could not find free port for `pub get`: $e');
-    } finally {
-      if (serverSocket != null) {
-        await serverSocket.close();
-      }
-    }
-
-    return port;
-  }
-
-  static const String _samplePackageName = 'dartpad_sample';
-
-  static String createPubspec(bool includeFlutterWeb) {
-    var content = '''
-name: $_samplePackageName
-''';
-
-    if (includeFlutterWeb) {
-      content += '''
-dependencies:
-  flutter:
-    sdk: flutter
-  flutter_test:
-    sdk: flutter
-''';
-    }
-
-    return content;
-  }
 }
diff --git a/pubspec.lock b/pubspec.lock
index 41bef28..3ec38df 100644
--- a/pubspec.lock
+++ b/pubspec.lock
@@ -70,14 +70,14 @@
       name: build
       url: "https://pub.dartlang.org"
     source: hosted
-    version: "1.5.1"
+    version: "1.5.2"
   build_config:
     dependency: transitive
     description:
       name: build_config
       url: "https://pub.dartlang.org"
     source: hosted
-    version: "0.4.3"
+    version: "0.4.4"
   build_daemon:
     dependency: transitive
     description:
@@ -91,21 +91,21 @@
       name: build_resolvers
       url: "https://pub.dartlang.org"
     source: hosted
-    version: "1.4.3"
+    version: "1.4.4"
   build_runner:
     dependency: "direct dev"
     description:
       name: build_runner
       url: "https://pub.dartlang.org"
     source: hosted
-    version: "1.10.6"
+    version: "1.10.7"
   build_runner_core:
     dependency: transitive
     description:
       name: build_runner_core
       url: "https://pub.dartlang.org"
     source: hosted
-    version: "6.1.1"
+    version: "6.1.2"
   built_collection:
     dependency: transitive
     description:
@@ -310,7 +310,7 @@
     source: hosted
     version: "0.16.1"
   io:
-    dependency: transitive
+    dependency: "direct main"
     description:
       name: io
       url: "https://pub.dartlang.org"
@@ -490,7 +490,7 @@
       name: source_gen
       url: "https://pub.dartlang.org"
     source: hosted
-    version: "0.9.8"
+    version: "0.9.9"
   source_map_stack_trace:
     dependency: transitive
     description:
@@ -560,7 +560,7 @@
       name: test
       url: "https://pub.dartlang.org"
     source: hosted
-    version: "1.15.6"
+    version: "1.15.7"
   test_api:
     dependency: transitive
     description:
@@ -574,7 +574,7 @@
       name: test_core
       url: "https://pub.dartlang.org"
     source: hosted
-    version: "0.3.11+3"
+    version: "0.3.11+4"
   timing:
     dependency: transitive
     description:
diff --git a/pubspec.yaml b/pubspec.yaml
index 7b947a1..5ae5ba9 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -14,6 +14,7 @@
   crypto: ^2.0.0
   dartis: ^0.5.0
   http: ^0.12.0
+  io: ^0.3.4
   logging: ^0.11.0
   meta: ^1.1.8
   path: ^1.6.2
diff --git a/test/flutter_analysis_server_test.dart b/test/flutter_analysis_server_test.dart
index 9791eca..99e3628 100644
--- a/test/flutter_analysis_server_test.dart
+++ b/test/flutter_analysis_server_test.dart
@@ -210,7 +210,6 @@
     setUp(() async {
       await SdkManager.flutterSdk.init();
       flutterWebManager = FlutterWebManager(SdkManager.flutterSdk);
-      await flutterWebManager.warmup();
       analysisServer = FlutterAnalysisServerWrapper(flutterWebManager);
       await analysisServer.init();
       await analysisServer.warmup();
@@ -218,7 +217,6 @@
 
     tearDown(() async {
       await analysisServer.shutdown();
-      await flutterWebManager.dispose();
     });
 
     test('analyze counter app', () async {
diff --git a/test/flutter_web_test.dart b/test/flutter_web_test.dart
index c5f3c45..ef8b121 100644
--- a/test/flutter_web_test.dart
+++ b/test/flutter_web_test.dart
@@ -2,10 +2,12 @@
 // for details. All rights reserved. Use of this source code is governed by a
 // BSD-style license that can be found in the LICENSE file.
 
+import 'dart:convert';
 import 'dart:io';
 
 import 'package:dart_services/src/flutter_web.dart';
 import 'package:dart_services/src/sdk_manager.dart';
+import 'package:path/path.dart' as path;
 import 'package:test/test.dart';
 
 void main() => defineTests();
@@ -20,14 +22,11 @@
       flutterWebManager = FlutterWebManager(SdkManager.flutterSdk);
     });
 
-    tearDown(() {
-      flutterWebManager.dispose();
-    });
-
-    test('inited', () {
-      expect(flutterWebManager.projectDirectory.existsSync(), isTrue);
-      final file = File(flutterWebManager.packagesFilePath);
-      expect(file.existsSync(), isTrue);
+    test('inited', () async {
+      expect(await flutterWebManager.flutterTemplateProject.exists(), isTrue);
+      final file = File(path.join(flutterWebManager.flutterTemplateProject.path,
+          '.dart_tool', 'package_config.json'));
+      expect(await file.exists(), isTrue);
     });
 
     test('usesFlutterWeb', () {
@@ -58,20 +57,20 @@
       await SdkManager.sdk.init();
       await SdkManager.flutterSdk.init();
       flutterWebManager = FlutterWebManager(SdkManager.flutterSdk);
-      await flutterWebManager.warmup();
     });
 
-    tearDownAll(() {
-      flutterWebManager.dispose();
-    });
-
-    test('packagesFilePath', () {
-      final packagesPath = flutterWebManager.packagesFilePath;
-      expect(packagesPath, isNotEmpty);
-
-      final file = File(packagesPath);
-      final lines = file.readAsLinesSync();
-      expect(lines, anyElement(startsWith('flutter:file://')));
+    test('packagesFilePath', () async {
+      final packageConfig = File(path.join(
+          flutterWebManager.flutterTemplateProject.path,
+          '.dart_tool',
+          'package_config.json'));
+      expect(await packageConfig.exists(), true);
+      final contents = jsonDecode(await packageConfig.readAsString());
+      expect(contents['packages'], isNotEmpty);
+      expect(
+          (contents['packages'] as List)
+              .where((element) => element['name'] == 'flutter'),
+          isNotEmpty);
     });
 
     test('summaryFilePath', () {
diff --git a/tool/grind.dart b/tool/grind.dart
index 8306d10..483b305 100644
--- a/tool/grind.dart
+++ b/tool/grind.dart
@@ -7,12 +7,12 @@
 import 'dart:async';
 import 'dart:io';
 
-import 'package:dart_services/src/flutter_web.dart';
 import 'package:dart_services/src/sdk_manager.dart';
 import 'package:grinder/grinder.dart';
 import 'package:grinder/grinder_files.dart';
 import 'package:grinder/src/run_utils.dart' show mergeWorkingDirectory;
 import 'package:http/http.dart' as http;
+import 'package:meta/meta.dart';
 import 'package:path/path.dart' as path;
 
 Future<void> main(List<String> args) async {
@@ -22,8 +22,8 @@
 }
 
 @Task()
-void analyze() {
-  Pub.run('tuneup', arguments: ['check']);
+void analyze() async {
+  await runWithLogging('dart', arguments: ['analyze']);
 }
 
 @Task()
@@ -86,7 +86,52 @@
   }
 }
 
+@Task('build the project templates')
+void buildProjectTemplates() async {
+  final templatesPath =
+      Directory(path.join(Directory.current.path, 'project_templates'));
+  final exists = await templatesPath.exists();
+  if (exists) {
+    await templatesPath.delete(recursive: true);
+  }
+
+  final dartProjectPath =
+      Directory(path.join(templatesPath.path, 'dart_project'));
+  final dartProjectDir = await dartProjectPath.create(recursive: true);
+  joinFile(dartProjectDir, ['pubspec.yaml'])
+      .writeAsStringSync(createPubspec(includeFlutterWeb: false));
+  await _runDartPubGet(dartProjectDir);
+
+  final flutterProjectPath =
+      Directory(path.join(templatesPath.path, 'flutter_project'));
+  final flutterProjectDir = await flutterProjectPath.create(recursive: true);
+  joinFile(flutterProjectDir, ['pubspec.yaml'])
+      .writeAsStringSync(createPubspec(includeFlutterWeb: true));
+  await _runFlutterPubGet(flutterProjectDir);
+}
+
+Future<void> _runDartPubGet(Directory dir) async {
+  log('running dart pub get (${dir.path})');
+
+  await runWithLogging(
+    path.join(SdkManager.sdk.sdkPath, 'bin', 'dart'),
+    arguments: ['pub', 'get'],
+    workingDirectory: dir.path,
+  );
+}
+
+Future<void> _runFlutterPubGet(Directory dir) async {
+  log('running flutter pub get (${dir.path})');
+
+  await runWithLogging(
+    path.join(SdkManager.flutterSdk.flutterBinPath, 'flutter'),
+    arguments: ['pub', 'get'],
+    workingDirectory: dir.path,
+  );
+}
+
 @Task('build the sdk compilation artifacts for upload to google storage')
+@Depends(buildProjectTemplates)
 void buildStorageArtifacts() async {
   // build and copy dart_sdk.js, flutter_web.js, and flutter_web.dill
   final temp = Directory.systemTemp.createTempSync('flutter_web_sample');
@@ -101,7 +146,7 @@
 void _buildStorageArtifacts(Directory dir) async {
   final flutterSdkPath =
       Directory(path.join(Directory.current.path, 'flutter'));
-  final pubspec = FlutterWebManager.createPubspec(true);
+  final pubspec = createPubspec(includeFlutterWeb: true);
   joinFile(dir, ['pubspec.yaml']).writeAsStringSync(pubspec);
 
   // run flutter pub get
@@ -142,7 +187,7 @@
   // Make sure flutter/bin/cache/flutter_web_sdk/flutter_web_sdk/kernel/flutter_ddc_sdk.dill
   // is installed.
   await runWithLogging(
-    path.join(flutterSdkPath.path, 'bin/flutter'),
+    path.join(flutterSdkPath.path, 'bin', 'flutter'),
     arguments: ['precache', '--web'],
     workingDirectory: dir.path,
   );
@@ -290,3 +335,26 @@
     fail('Unable to exec $executable, failed with code $exitCode');
   }
 }
+
+const String _samplePackageName = 'dartpad_sample';
+
+String createPubspec({@required bool includeFlutterWeb}) {
+  // Mark the samples as not null safe.
+  var content = '''
+name: $_samplePackageName
+environment:
+  sdk: '>=2.10.0 <3.0.0'
+''';
+
+  if (includeFlutterWeb) {
+    content += '''
+dependencies:
+  flutter:
+    sdk: flutter
+  flutter_test:
+    sdk: flutter
+''';
+  }
+
+  return content;
+}