Add hook to use single-root in fasta scripts and make use of it when creating
the dart2js platform files.

The main use-case right now is in compile-platform, but the way it is implemented
makes this available to any fasta tool.

This also adds resolveInputUri so we don't incorrectly treat supported URIs as file:* Uris.

Fixes https://github.com/dart-lang/sdk/issues/32633

Change-Id: I0ea3b1128352e72957b35823c6a1749ee418768b
Reviewed-on: https://dart-review.googlesource.com/56282
Reviewed-by: Zach Anderson <zra@google.com>
Reviewed-by: Peter von der Ahé <ahe@google.com>
Commit-Queue: Sigmund Cherem <sigmund@google.com>
diff --git a/pkg/front_end/lib/src/api_prototype/scheme_based_file_system.dart b/pkg/front_end/lib/src/scheme_based_file_system.dart
similarity index 94%
rename from pkg/front_end/lib/src/api_prototype/scheme_based_file_system.dart
rename to pkg/front_end/lib/src/scheme_based_file_system.dart
index c992ea7..c2b3981 100644
--- a/pkg/front_end/lib/src/api_prototype/scheme_based_file_system.dart
+++ b/pkg/front_end/lib/src/scheme_based_file_system.dart
@@ -2,7 +2,7 @@
 // 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 'file_system.dart';
+import 'api_prototype/file_system.dart';
 
 /// A [FileSystem] that delegates to other file systems based on the URI scheme.
 class SchemeBasedFileSystem implements FileSystem {
diff --git a/pkg/front_end/pubspec.yaml b/pkg/front_end/pubspec.yaml
index 4ba2dc2..86c698f 100644
--- a/pkg/front_end/pubspec.yaml
+++ b/pkg/front_end/pubspec.yaml
@@ -20,6 +20,7 @@
 dev_dependencies:
   analyzer: '>=0.31.0 <0.33.0'
   args: '>=0.13.0 <2.0.0'
+  build_integration: ^0.0.1
   dart_style: '^1.0.7'
   json_rpc_2: ^2.0.4
   mockito: ^2.0.2
diff --git a/pkg/front_end/test/scheme_based_file_system_test.dart b/pkg/front_end/test/scheme_based_file_system_test.dart
index be40f65..e6b374b 100644
--- a/pkg/front_end/test/scheme_based_file_system_test.dart
+++ b/pkg/front_end/test/scheme_based_file_system_test.dart
@@ -3,7 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 
 import 'package:front_end/src/api_prototype/file_system.dart';
-import 'package:front_end/src/api_prototype/scheme_based_file_system.dart';
+import 'package:front_end/src/scheme_based_file_system.dart';
 
 import 'package:test/test.dart';
 
diff --git a/pkg/front_end/tool/_fasta/command_line.dart b/pkg/front_end/tool/_fasta/command_line.dart
index daceb3cd..8408682 100644
--- a/pkg/front_end/tool/_fasta/command_line.dart
+++ b/pkg/front_end/tool/_fasta/command_line.dart
@@ -6,9 +6,17 @@
 
 import 'dart:io' show exit;
 
+import 'package:build_integration/file_system/single_root.dart'
+    show SingleRootFileSystem;
+
 import 'package:front_end/src/api_prototype/compiler_options.dart'
     show CompilerOptions;
 
+import 'package:front_end/src/api_prototype/file_system.dart' show FileSystem;
+
+import 'package:front_end/src/api_prototype/standard_file_system.dart'
+    show StandardFileSystem;
+
 import 'package:front_end/src/base/processed_options.dart'
     show ProcessedOptions;
 
@@ -29,10 +37,13 @@
 
 import 'package:front_end/src/fasta/severity.dart' show Severity;
 
+import 'package:front_end/src/scheme_based_file_system.dart'
+    show SchemeBasedFileSystem;
+
 import 'package:kernel/target/targets.dart'
     show Target, getTarget, TargetFlags, targets;
 
-Uri resolveFile(String path) => Uri.base.resolveUri(new Uri.file(path));
+import 'resolve_input_uri.dart' show resolveInputUri;
 
 class CommandLineProblem {
   final Message message;
@@ -159,7 +170,9 @@
                     "but expected one of: 'true', 'false', 'yes', or 'no'.");
               }
             } else if (valueSpecification == Uri) {
-              parsedValue = resolveFile(value);
+              // TODO(ahe): resolve Uris lazily, so that schemes provided by
+              // other flags can be used for parsed command-line arguments too.
+              parsedValue = resolveInputUri(value);
             } else if (valueSpecification == String) {
               parsedValue = value;
             } else if (valueSpecification is String) {
@@ -210,6 +223,8 @@
   "--packages": Uri,
   "--platform": Uri,
   "--sdk": Uri,
+  "--single-root-scheme": String,
+  "--single-root-base": Uri,
   "--strong": "--strong-mode",
   "--strong-mode": false,
   "--sync-async": true,
@@ -279,6 +294,24 @@
 
   final bool compileSdk = options.containsKey("--compile-sdk");
 
+  final String singleRootScheme = options["--single-root-scheme"];
+  final Uri singleRootBase = options["--single-root-base"];
+
+  FileSystem fileSystem = StandardFileSystem.instance;
+  List<String> extraSchemes = const [];
+  if (singleRootScheme != null) {
+    extraSchemes = [singleRootScheme];
+    fileSystem = new SchemeBasedFileSystem({
+      'file': fileSystem,
+      'data': fileSystem,
+      // TODO(askesc): remove also when fixing StandardFileSystem (empty schemes
+      // should have been handled elsewhere).
+      '': fileSystem,
+      singleRootScheme: new SingleRootFileSystem(
+          singleRootScheme, singleRootBase, fileSystem),
+    });
+  }
+
   if (programName == "compile_platform") {
     if (arguments.length != 4) {
       return throw new CommandLineProblem.deprecated(
@@ -296,8 +329,10 @@
     return new ProcessedOptions(
         new CompilerOptions()
           ..sdkSummary = options["--platform"]
-          ..librariesSpecificationUri = resolveFile(arguments[1])
+          ..librariesSpecificationUri =
+              resolveInputUri(arguments[1], extraSchemes: extraSchemes)
           ..setExitCodeOnProblem = true
+          ..fileSystem = fileSystem
           ..packagesFileUri = packages
           ..strongMode = strongMode
           ..target = target
@@ -308,12 +343,13 @@
           ..verbose = verbose
           ..verify = verify,
         <Uri>[Uri.parse(arguments[0])],
-        resolveFile(arguments[2]));
+        resolveInputUri(arguments[2], extraSchemes: extraSchemes));
   } else if (arguments.isEmpty) {
     return throw new CommandLineProblem.deprecated("No Dart file specified.");
   }
 
-  final Uri defaultOutput = resolveFile("${arguments.first}.dill");
+  final Uri defaultOutput =
+      resolveInputUri("${arguments.first}.dill", extraSchemes: extraSchemes);
 
   final Uri output = options["-o"] ?? options["--output"] ?? defaultOutput;
 
@@ -326,6 +362,7 @@
 
   CompilerOptions compilerOptions = new CompilerOptions()
     ..compileSdk = compileSdk
+    ..fileSystem = fileSystem
     ..sdkRoot = sdk
     ..sdkSummary = platform
     ..packagesFileUri = packages
@@ -343,7 +380,7 @@
   List<Uri> inputs = <Uri>[];
   if (areRestArgumentsInputs) {
     for (String argument in arguments) {
-      inputs.add(resolveFile(argument));
+      inputs.add(resolveInputUri(argument, extraSchemes: extraSchemes));
     }
   }
   return new ProcessedOptions(compilerOptions, inputs, output);
diff --git a/pkg/front_end/tool/_fasta/resolve_input_uri.dart b/pkg/front_end/tool/_fasta/resolve_input_uri.dart
new file mode 100644
index 0000000..f0b49d5
--- /dev/null
+++ b/pkg/front_end/tool/_fasta/resolve_input_uri.dart
@@ -0,0 +1,19 @@
+// Copyright (c) 2018, the Dart project authors.  Please see the AUTHORS file
+// 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.
+
+/// Resolve [string] as a [Uri].  If the input [string] starts with a supported
+/// scheme, it is resolved as a Uri of that scheme, otherwise the [string]
+/// is treated as a file system (possibly relative) path.
+///
+/// Three schemes are always supported by default: `dart`, `package`, and
+/// `data`. Additional supported schemes can be specified via [extraSchemes].
+Uri resolveInputUri(String string, {List<String> extraSchemes: const []}) {
+  if (string.startsWith('dart:')) return Uri.parse(string);
+  if (string.startsWith('data:')) return Uri.parse(string);
+  if (string.startsWith('package:')) return Uri.parse(string);
+  for (var scheme in extraSchemes) {
+    if (string.startsWith('$scheme:')) return Uri.parse(string);
+  }
+  return Uri.base.resolveUri(new Uri.file(string));
+}
diff --git a/pkg/front_end/tool/_fasta/resolve_input_uri_test.dart b/pkg/front_end/tool/_fasta/resolve_input_uri_test.dart
new file mode 100644
index 0000000..7020ffc
--- /dev/null
+++ b/pkg/front_end/tool/_fasta/resolve_input_uri_test.dart
@@ -0,0 +1,31 @@
+// Copyright (c) 2018, the Dart project authors.  Please see the AUTHORS file
+// 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 'package:test/test.dart';
+
+import 'resolve_input_uri.dart';
+
+main() {
+  test('data URI scheme is supported by default', () {
+    expect(resolveInputUri('data:,foo').scheme, 'data');
+  });
+
+  test('internal dart schemes are recognized by default', () {
+    expect(resolveInputUri('dart:foo').scheme, 'dart');
+    expect(resolveInputUri('package:foo').scheme, 'package');
+  });
+
+  test('unknown schemes are not recognized by default', () {
+    expect(resolveInputUri('test:foo').scheme, 'file');
+    expect(resolveInputUri('org-dartlang-foo:bar').scheme, 'file');
+  });
+
+  test('more schemes can be supported', () {
+    expect(resolveInputUri('test:foo', extraSchemes: ['test']).scheme, 'test');
+    expect(
+        resolveInputUri('org-dartlang-foo:bar',
+            extraSchemes: ['org-dartlang-foo']).scheme,
+        'org-dartlang-foo');
+  });
+}
diff --git a/utils/compile_platform.gni b/utils/compile_platform.gni
index 68ade10..4601662 100644
--- a/utils/compile_platform.gni
+++ b/utils/compile_platform.gni
@@ -17,6 +17,14 @@
   assert(defined(invoker.sources), "Need 'sources' in $target_name")
   assert(defined(invoker.outputs), "Need 'outputs' in $target_name")
   assert(defined(invoker.args), "Need 'args' in $target_name")
+  if (defined(invoker.single_root_scheme)) {
+    assert(defined(invoker.single_root_base),
+        "Need 'single_root_base' in $target_name")
+  }
+  if (defined(invoker.single_root_base)) {
+    assert(defined(invoker.single_root_scheme),
+        "Need 'single_root_scheme' in $target_name")
+  }
   assert(!defined(invoker.script), "Remove 'script' from $target_name")
   assert(!defined(invoker.depfile), "Remove 'depfile' from $target_name")
 
@@ -35,7 +43,15 @@
   action(target_name) {
     script = "$_dart_root/tools/compile_platform.py"
 
-    sources = invoker.sources
+    # Note: invoker.sources contains a library-specification URI. It's not
+    # listed as a source of the action because it may be written using a custom
+    # URI when this template is invoked with a single_root_scheme.
+    #
+    # The compile_platform script, however, generates a deps file which includes
+    # the corresponding library-specification file URI, so we rely on that to
+    # track the required dependencies.
+    # TODO(sigmund): replace "sources" with a "library_specification_uri"
+    # argument to make this clearer.
 
     outputs = invoker.outputs
 
@@ -74,7 +90,17 @@
     }
 
     args += invoker.args
-    args += rebase_path(sources, root_build_dir)
+    if (defined(invoker.single_root_scheme)) {
+      args += ["--single-root-scheme=" + invoker.single_root_scheme]
+    }
+    if (defined(invoker.single_root_base)) {
+      args += ["--single-root-base=" + invoker.single_root_base]
+    }
+    if (defined(invoker.single_root_scheme)) {
+      args += invoker.sources
+    } else {
+      args += rebase_path(invoker.sources, root_build_dir)
+    }
     args += rebase_path(outputs, root_build_dir)
   }
 }
diff --git a/utils/compiler/BUILD.gn b/utils/compiler/BUILD.gn
index fbc9258..428a2e0 100644
--- a/utils/compiler/BUILD.gn
+++ b/utils/compiler/BUILD.gn
@@ -76,8 +76,10 @@
 }
 
 compile_platform("compile_dart2js_platform") {
+  single_root_scheme = "org-dartlang-sdk"
+  single_root_base = "../../"
   sources = [
-    "../../sdk/lib/libraries.json",
+    "org-dartlang-sdk:///sdk/lib/libraries.json",
   ]
 
   outputs = [
@@ -92,8 +94,10 @@
 }
 
 compile_platform("compile_dart2js_platform_strong") {
+  single_root_scheme = "org-dartlang-sdk"
+  single_root_base = "../../"
   sources = [
-    "../../sdk/lib/libraries.json",
+    "org-dartlang-sdk:///sdk/lib/libraries.json",
   ]
 
   outputs = [
@@ -109,8 +113,10 @@
 }
 
 compile_platform("compile_dart2js_server_platform") {
+  single_root_scheme = "org-dartlang-sdk"
+  single_root_base = "../../"
   sources = [
-    "../../sdk/lib/libraries.json",
+    "org-dartlang-sdk:///sdk/lib/libraries.json",
   ]
 
   outputs = [
@@ -125,8 +131,10 @@
 }
 
 compile_platform("compile_dart2js_server_platform_strong") {
+  single_root_scheme = "org-dartlang-sdk"
+  single_root_base = "../../"
   sources = [
-    "../../sdk/lib/libraries.json",
+    "org-dartlang-sdk:///sdk/lib/libraries.json",
   ]
 
   outputs = [