Teach SkyShell about .packages files

SkyShell now supports --packages instead of --package-root.
diff --git a/sky/build/PackagerInvoke b/sky/build/PackagerInvoke
index 0a80ea9..45dd598 100755
--- a/sky/build/PackagerInvoke
+++ b/sky/build/PackagerInvoke
@@ -41,8 +41,8 @@
   local dart_main=${project_path}/lib/main.dart
   AssertExists $dart_main
 
-  local package_root=${project_path}/packages
-  AssertExists $package_root
+  local packages=${project_path}/.packages
+  AssertExists $packages
 
   # Remove old build artifacts
   RunCommand rm -f ${derived_dir}/app.flx
@@ -62,7 +62,7 @@
   RunCommand ${FLUTTER_ROOT}/bin/flutter build flx                             \
       --target ${dart_main}                                                    \
       --output-file ${derived_dir}/app.flx                                     \
-      --package-root ${package_root}                                           \
+      --packages ${packages}                                                   \
       --compiler ${src_dir}/ScriptSnapshotter                                  \
       ${precompilation_flag}                                                   \
 
diff --git a/sky/build/sdk_xcode_harness/FlutterApplication.xcodeproj/xcshareddata/xcschemes/Application.xcscheme b/sky/build/sdk_xcode_harness/FlutterApplication.xcodeproj/xcshareddata/xcschemes/Application.xcscheme
index 0d1f0d7..6f69f30 100644
--- a/sky/build/sdk_xcode_harness/FlutterApplication.xcodeproj/xcshareddata/xcschemes/Application.xcscheme
+++ b/sky/build/sdk_xcode_harness/FlutterApplication.xcodeproj/xcshareddata/xcschemes/Application.xcscheme
@@ -71,7 +71,7 @@
             isEnabled = "YES">
          </CommandLineArgument>
          <CommandLineArgument
-            argument = "--package-root=$(FLUTTER_APPLICATION_PATH)/packages"
+            argument = "--packages=$(FLUTTER_APPLICATION_PATH)/.packages"
             isEnabled = "YES">
          </CommandLineArgument>
       </CommandLineArguments>
diff --git a/sky/services/engine/sky_engine.mojom b/sky/services/engine/sky_engine.mojom
index 34bb2b3..2ec7457 100644
--- a/sky/services/engine/sky_engine.mojom
+++ b/sky/services/engine/sky_engine.mojom
@@ -49,7 +49,7 @@
   PushRoute(string route);
   PopRoute();
 
-  RunFromFile(string main, string package_root, string bundle_path);
+  RunFromFile(string main, string packages, string bundle_path);
   RunFromPrecompiledSnapshot(string bundle_path);
   RunFromBundle(string script_uri, string bundle_path);
 
diff --git a/sky/shell/dart/BUILD.gn b/sky/shell/dart/BUILD.gn
index d56f1c1..706b69f 100644
--- a/sky/shell/dart/BUILD.gn
+++ b/sky/shell/dart/BUILD.gn
@@ -15,6 +15,7 @@
     "//mojo/data_pipe_utils",
     "//mojo/public/cpp/application",
     "//sky/engine/tonic",
+    "//sky/engine/tonic/parsers",
     "//sky/engine/wtf",
   ]
 }
diff --git a/sky/shell/dart/dart_library_provider_files.cc b/sky/shell/dart/dart_library_provider_files.cc
index f7f6ed2..c82f511 100644
--- a/sky/shell/dart/dart_library_provider_files.cc
+++ b/sky/shell/dart/dart_library_provider_files.cc
@@ -39,18 +39,28 @@
 
 }  // namespace
 
-DartLibraryProviderFiles::DartLibraryProviderFiles(
-    const base::FilePath& package_root)
-    : package_root_(package_root) {
-    if (package_root_.empty())
-      package_root_ = base::FilePath(FILE_PATH_LITERAL("packages"));
-    if (!base::DirectoryExists(package_root_))
-      package_root_ = base::FilePath();
+DartLibraryProviderFiles::DartLibraryProviderFiles() {
 }
 
 DartLibraryProviderFiles::~DartLibraryProviderFiles() {
 }
 
+void DartLibraryProviderFiles::LoadPackagesMap(const base::FilePath& packages) {
+  packages_ = base::MakeAbsoluteFilePath(packages);
+  std::string packages_source;
+  if (!base::ReadFileToString(packages_, &packages_source)) {
+    LOG(ERROR) << "error: Unable to load .packages file '"
+               << packages_.AsUTF8Unsafe() << "'.";
+    exit(1);
+  }
+  std::string error;
+  if (!packages_map_.Parse(packages_source, &error)) {
+    LOG(ERROR) << "error: Unable to parse .packages file '"
+               << packages_.AsUTF8Unsafe() << "'.\n" << error;
+    exit(1);
+  }
+}
+
 void DartLibraryProviderFiles::GetLibraryAsStream(
     const std::string& name,
     blink::DataPipeConsumerCallback callback) {
@@ -67,9 +77,20 @@
 std::string DartLibraryProviderFiles::CanonicalizePackageURL(std::string url) {
   DCHECK(base::StartsWithASCII(url, "package:", true));
   base::ReplaceFirstSubstringAfterOffset(&url, 0, "package:", "");
-  CHECK(!package_root_.empty())
-      << "Cannot import packages without a valid --package-root";
-  return package_root_.Append(url).AsUTF8Unsafe();
+  size_t slash = url.find('/');
+  if (slash == std::string::npos)
+    return std::string();
+  std::string package = url.substr(0, slash);
+  std::string library_path = url.substr(slash + 1);
+  std::string package_path = packages_map_.Resolve(package);
+  if (package_path.empty())
+    return std::string();
+  if (base::StartsWithASCII(package_path, "file://", true)) {
+    base::ReplaceFirstSubstringAfterOffset(&package_path, 0, "file://", "");
+    return package_path + library_path;
+  }
+  auto path = packages_.DirName().Append(package_path).Append(library_path);
+  return SimplifyPath(path).AsUTF8Unsafe();
 }
 
 std::string DartLibraryProviderFiles::CanonicalizeFileURL(std::string url) {
diff --git a/sky/shell/dart/dart_library_provider_files.h b/sky/shell/dart/dart_library_provider_files.h
index 90fb77c..7b9135f 100644
--- a/sky/shell/dart/dart_library_provider_files.h
+++ b/sky/shell/dart/dart_library_provider_files.h
@@ -7,15 +7,18 @@
 
 #include "base/files/file_path.h"
 #include "sky/engine/tonic/dart_library_provider.h"
+#include "sky/engine/tonic/parsers/packages_map.h"
 
 namespace sky {
 namespace shell {
 
 class DartLibraryProviderFiles : public blink::DartLibraryProvider {
  public:
-  explicit DartLibraryProviderFiles(const base::FilePath& package_root);
+  DartLibraryProviderFiles();
   ~DartLibraryProviderFiles() override;
 
+  void LoadPackagesMap(const base::FilePath& packages);
+
  protected:
   // |DartLibraryProvider| implementation:
   void GetLibraryAsStream(const std::string& name,
@@ -26,7 +29,8 @@
   std::string CanonicalizePackageURL(std::string url);
   std::string CanonicalizeFileURL(std::string url);
 
-  base::FilePath package_root_;
+  base::FilePath packages_;
+  tonic::PackagesMap packages_map_;
 
   DISALLOW_COPY_AND_ASSIGN(DartLibraryProviderFiles);
 };
diff --git a/sky/shell/platform/ios/FlutterAppDelegate.mm b/sky/shell/platform/ios/FlutterAppDelegate.mm
index 1cde42b..e88f7cb 100644
--- a/sky/shell/platform/ios/FlutterAppDelegate.mm
+++ b/sky/shell/platform/ios/FlutterAppDelegate.mm
@@ -32,7 +32,7 @@
   FlutterDartProject* project = [[FlutterDartProject alloc]
       initWithFLXArchive:URLForSwitch(sky::shell::switches::kFLX)
                 dartMain:URLForSwitch(sky::shell::switches::kMainDartFile)
-             packageRoot:URLForSwitch(sky::shell::switches::kPackageRoot)];
+                packages:URLForSwitch(sky::shell::switches::kPackages)];
 #else
   NSString* bundlePath =
       [[NSBundle mainBundle] pathForResource:@"FlutterApplication"
diff --git a/sky/shell/platform/ios/FlutterDartProject.mm b/sky/shell/platform/ios/FlutterDartProject.mm
index cb12062..51b7324 100644
--- a/sky/shell/platform/ios/FlutterDartProject.mm
+++ b/sky/shell/platform/ios/FlutterDartProject.mm
@@ -15,7 +15,7 @@
 #pragma mark - Override base class designated initializers
 
 - (instancetype)init {
-  return [self initWithFLXArchive:nil dartMain:nil packageRoot:nil];
+  return [self initWithFLXArchive:nil dartMain:nil packages:nil];
 }
 
 #pragma mark - Designated initializers
@@ -34,12 +34,12 @@
 
 - (instancetype)initWithFLXArchive:(NSURL*)archiveURL
                           dartMain:(NSURL*)dartMainURL
-                       packageRoot:(NSURL*)dartPackageURL {
+                          packages:(NSURL*)dartPackages {
   self = [super init];
 
   if (self) {
     _dartSource = [[FlutterDartSource alloc] initWithDartMain:dartMainURL
-                                                  packageRoot:dartPackageURL
+                                                     packages:dartPackages
                                                    flxArchive:archiveURL];
 
     [self checkReadiness];
@@ -160,7 +160,7 @@
     }
 
     engine->RunFromFile(_dartSource.dartMain.absoluteURL.path.UTF8String,
-                        _dartSource.packageRoot.absoluteURL.path.UTF8String,
+                        _dartSource.packages.absoluteURL.path.UTF8String,
                         _dartSource.flxArchive.absoluteURL.path.UTF8String);
     result(YES, @"Success");
   }];
diff --git a/sky/shell/platform/ios/FlutterDartSource.h b/sky/shell/platform/ios/FlutterDartSource.h
index 72391fe..51502b4 100644
--- a/sky/shell/platform/ios/FlutterDartSource.h
+++ b/sky/shell/platform/ios/FlutterDartSource.h
@@ -12,11 +12,11 @@
 @interface FlutterDartSource : NSObject
 
 @property(nonatomic, readonly) NSURL* dartMain;
-@property(nonatomic, readonly) NSURL* packageRoot;
+@property(nonatomic, readonly) NSURL* packages;
 @property(nonatomic, readonly) NSURL* flxArchive;
 
 - (instancetype)initWithDartMain:(NSURL*)dartMain
-                     packageRoot:(NSURL*)packageRoot
+                        packages:(NSURL*)packages
                       flxArchive:(NSURL*)flxArchive NS_DESIGNATED_INITIALIZER;
 
 - (void)validate:(ValidationResult)result;
diff --git a/sky/shell/platform/ios/FlutterDartSource.mm b/sky/shell/platform/ios/FlutterDartSource.mm
index 2b9ec5f..55d93bf 100644
--- a/sky/shell/platform/ios/FlutterDartSource.mm
+++ b/sky/shell/platform/ios/FlutterDartSource.mm
@@ -6,22 +6,22 @@
 
 @implementation FlutterDartSource {
   NSURL* _dartMain;
-  NSURL* _packageRoot;
+  NSURL* _packages;
   NSURL* _flxArchive;
 }
 
 - (instancetype)init {
-  return [self initWithDartMain:nil packageRoot:nil flxArchive:nil];
+  return [self initWithDartMain:nil packages:nil flxArchive:nil];
 }
 
 - (instancetype)initWithDartMain:(NSURL*)dartMain
-                     packageRoot:(NSURL*)packageRoot
+                        packages:(NSURL*)packages
                       flxArchive:(NSURL*)flxArchive {
   self = [super init];
 
   if (self) {
     _dartMain = [dartMain copy];
-    _packageRoot = [packageRoot copy];
+    _packages = [packages copy];
     _flxArchive = [flxArchive copy];
   }
 
@@ -57,14 +57,14 @@
 
   isValid &= CheckDartProjectURL(log, _flxArchive, @"FLX archive");
   isValid &= CheckDartProjectURL(log, _dartMain, @"Dart main");
-  isValid &= CheckDartProjectURL(log, _packageRoot, @"Dart package root");
+  isValid &= CheckDartProjectURL(log, _packages, @"Dart packages");
 
   result(isValid, log);
 }
 
 - (void)dealloc {
   [_dartMain release];
-  [_packageRoot release];
+  [_packages release];
   [_flxArchive release];
 
   [super dealloc];
diff --git a/sky/shell/platform/ios/public/FlutterDartProject.h b/sky/shell/platform/ios/public/FlutterDartProject.h
index 5f18203..e0643e3 100644
--- a/sky/shell/platform/ios/public/FlutterDartProject.h
+++ b/sky/shell/platform/ios/public/FlutterDartProject.h
@@ -17,7 +17,7 @@
 
 - (instancetype)initWithFLXArchive:(NSURL*)archiveURL
                           dartMain:(NSURL*)dartMainURL
-                       packageRoot:(NSURL*)dartPackageURL
+                          packages:(NSURL*)dartPackages
     NS_DESIGNATED_INITIALIZER;
 
 @end
diff --git a/sky/shell/platform/mac/platform_mac.mm b/sky/shell/platform/mac/platform_mac.mm
index c1633ae..ba20c90 100644
--- a/sky/shell/platform/mac/platform_mac.mm
+++ b/sky/shell/platform/mac/platform_mac.mm
@@ -103,9 +103,9 @@
 }
 
 static bool FlagsValidForCommandLineLaunch(const std::string& dart_main,
-                                           const std::string& package_root,
+                                           const std::string& packages,
                                            const std::string& bundle) {
-  if (dart_main.size() == 0 || package_root.size() == 0 || bundle.size() == 0) {
+  if (dart_main.empty() || packages.empty() || bundle.empty()) {
     return false;
   }
 
@@ -119,7 +119,7 @@
     return false;
   }
 
-  if (![manager fileExistsAtPath:@(package_root.c_str())]) {
+  if (![manager fileExistsAtPath:@(packages.c_str())]) {
     return false;
   }
 
@@ -157,23 +157,23 @@
   auto command_line = *base::CommandLine::ForCurrentProcess();
 
   if (command_line.HasSwitch(kMainDartFile) ||
-      command_line.HasSwitch(kPackageRoot) || command_line.HasSwitch(kFLX)) {
+      command_line.HasSwitch(kPackages) || command_line.HasSwitch(kFLX)) {
     // The main dart file, flx bundle and the package root must be specified in
     // one go. We dont want to end up in a situation where we take one value
     // from the command line and the others from user defaults. In case, any
     // new flags are specified, forget about all the old ones.
     [defaults removeObjectForKey:@(kMainDartFile)];
-    [defaults removeObjectForKey:@(kPackageRoot)];
+    [defaults removeObjectForKey:@(kPackages)];
     [defaults removeObjectForKey:@(kFLX)];
 
     [defaults synchronize];
   }
 
   std::string dart_main = ResolveCommandLineLaunchFlag(kMainDartFile);
-  std::string package_root = ResolveCommandLineLaunchFlag(kPackageRoot);
+  std::string packages = ResolveCommandLineLaunchFlag(kPackages);
   std::string bundle = ResolveCommandLineLaunchFlag(kFLX);
 
-  if (!FlagsValidForCommandLineLaunch(dart_main, package_root, bundle)) {
+  if (!FlagsValidForCommandLineLaunch(dart_main, packages, bundle)) {
     return false;
   }
 
@@ -181,13 +181,13 @@
   // defaults so that the next time the user launches the application in the
   // simulator without the tooling, the application boots up.
   [defaults setObject:@(dart_main.c_str()) forKey:@(kMainDartFile)];
-  [defaults setObject:@(package_root.c_str()) forKey:@(kPackageRoot)];
+  [defaults setObject:@(packages.c_str()) forKey:@(kPackages)];
   [defaults setObject:@(bundle.c_str()) forKey:@(kFLX)];
 
   [defaults synchronize];
 
   // Finally launch with the newly resolved arguments.
-  engine->RunFromFile(dart_main, package_root, bundle);
+  engine->RunFromFile(dart_main, packages, bundle);
   return true;
 }
 
diff --git a/sky/shell/platform/mac/sky_window.mm b/sky/shell/platform/mac/sky_window.mm
index 8757169..f83a475 100644
--- a/sky/shell/platform/mac/sky_window.mm
+++ b/sky/shell/platform/mac/sky_window.mm
@@ -109,9 +109,9 @@
 
   auto args = command_line.GetArgs();
   if (args.size() > 0) {
-    auto package_root =
-        command_line.GetSwitchValueASCII(sky::shell::switches::kPackageRoot);
-    _sky_engine->RunFromFile(args[0], package_root, "");
+    auto packages =
+        command_line.GetSwitchValueASCII(sky::shell::switches::kPackages);
+    _sky_engine->RunFromFile(args[0], packages, "");
     return;
   }
 }
diff --git a/sky/shell/switches.cc b/sky/shell/switches.cc
index f5daafd..46859e3 100644
--- a/sky/shell/switches.cc
+++ b/sky/shell/switches.cc
@@ -15,7 +15,7 @@
 const char kHelp[] = "help";
 const char kNonInteractive[] = "non-interactive";
 const char kMainDartFile[] = "dart-main";
-const char kPackageRoot[] = "package-root";
+const char kPackages[] = "packages";
 const char kStartPaused[] = "start-paused";
 const char kTraceStartup[] = "trace-startup";
 const char kDeviceObservatoryPort[] = "observatory-port";
@@ -27,7 +27,7 @@
             << " --" << kStartPaused
             << " --" << kTraceStartup
             << " --" << kFLX << "=FLX"
-            << " --" << kPackageRoot << "=PACKAGE_ROOT"
+            << " --" << kPackages << "=PACKAGES"
             << " --" << kDeviceObservatoryPort << "=8181"
             << " [ MAIN_DART ]" << std::endl;
 }
diff --git a/sky/shell/switches.h b/sky/shell/switches.h
index 6c08236..fbc6a39 100644
--- a/sky/shell/switches.h
+++ b/sky/shell/switches.h
@@ -16,7 +16,7 @@
 extern const char kHelp[];
 extern const char kNonInteractive[];
 extern const char kMainDartFile[];
-extern const char kPackageRoot[];
+extern const char kPackages[];
 extern const char kStartPaused[];
 extern const char kTraceStartup[];
 extern const char kDeviceObservatoryPort[];
diff --git a/sky/shell/testing/test_runner.cc b/sky/shell/testing/test_runner.cc
index 767421e..5043dde 100644
--- a/sky/shell/testing/test_runner.cc
+++ b/sky/shell/testing/test_runner.cc
@@ -41,7 +41,7 @@
 }
 
 void TestRunner::Run(const TestDescriptor& test) {
-  sky_engine_->RunFromFile(test.path, test.package_root, "");
+  sky_engine_->RunFromFile(test.path, test.packages, "");
 }
 
 }  // namespace shell
diff --git a/sky/shell/testing/test_runner.h b/sky/shell/testing/test_runner.h
index fbb27a6..bed4329 100644
--- a/sky/shell/testing/test_runner.h
+++ b/sky/shell/testing/test_runner.h
@@ -24,7 +24,7 @@
 
   struct TestDescriptor {
     std::string path;
-    std::string package_root;
+    std::string packages;
   };
 
   void Run(const TestDescriptor& test);
diff --git a/sky/shell/testing/testing.cc b/sky/shell/testing/testing.cc
index afa59ed..5f46a49 100644
--- a/sky/shell/testing/testing.cc
+++ b/sky/shell/testing/testing.cc
@@ -15,7 +15,7 @@
   base::CommandLine& command_line = *base::CommandLine::ForCurrentProcess();
 
   TestRunner::TestDescriptor test;
-  test.package_root = command_line.GetSwitchValueASCII(switches::kPackageRoot);
+  test.packages = command_line.GetSwitchValueASCII(switches::kPackages);
   auto args = command_line.GetArgs();
   if (args.empty())
     return false;
diff --git a/sky/shell/ui/engine.cc b/sky/shell/ui/engine.cc
index e674d7e..0b1d339 100644
--- a/sky/shell/ui/engine.cc
+++ b/sky/shell/ui/engine.cc
@@ -6,6 +6,7 @@
 
 #include "base/bind.h"
 #include "base/files/file_path.h"
+#include "base/files/file_util.h"
 #include "base/threading/worker_pool.h"
 #include "base/time/time.h"
 #include "base/trace_event/trace_event.h"
@@ -209,17 +210,31 @@
 }
 
 void Engine::RunFromFile(const mojo::String& main,
-                         const mojo::String& package_root,
+                         const mojo::String& packages,
                          const mojo::String& bundle) {
   TRACE_EVENT0("flutter", "Engine::RunFromFile");
+  std::string main_str(main);
   if (bundle.size() != 0) {
     // The specification of an FLX bundle is optional.
     ConfigureZipAssetBundle(bundle);
   }
-  std::string package_root_str = package_root;
-  dart_library_provider_.reset(
-      new DartLibraryProviderFiles(base::FilePath(package_root_str)));
-  RunFromLibrary(main);
+  base::FilePath packages_path = base::FilePath(std::string(packages));
+  if (packages_path.empty()) {
+    base::FilePath main_dir = base::FilePath(main_str).DirName();
+    packages_path = main_dir.Append(FILE_PATH_LITERAL(".packages"));
+    if (!base::PathExists(packages_path)) {
+      packages_path = main_dir
+          .Append(base::FilePath::kParentDirectory)
+          .Append(FILE_PATH_LITERAL(".packages"));
+      if (!base::PathExists(packages_path))
+        packages_path = base::FilePath();
+    }
+  }
+  DartLibraryProviderFiles* provider = new DartLibraryProviderFiles();
+  dart_library_provider_.reset(provider);
+  if (!packages_path.empty())
+    provider->LoadPackagesMap(packages_path);
+  RunFromLibrary(main_str);
 }
 
 void Engine::RunFromBundle(const mojo::String& script_uri,
diff --git a/sky/shell/ui/engine.h b/sky/shell/ui/engine.h
index de2f1e4..85e81f2 100644
--- a/sky/shell/ui/engine.h
+++ b/sky/shell/ui/engine.h
@@ -71,7 +71,7 @@
   void OnPointerPacket(pointer::PointerPacketPtr packet) override;
 
   void RunFromFile(const mojo::String& main,
-                   const mojo::String& package_root,
+                   const mojo::String& packages,
                    const mojo::String& bundle) override;
   void RunFromPrecompiledSnapshot(const mojo::String& bundle_path) override;
   void RunFromBundle(const mojo::String& script_uri,
diff --git a/sky/tools/sky_snapshot/loader.cc b/sky/tools/sky_snapshot/loader.cc
index 5af35ec..111ccf1 100644
--- a/sky/tools/sky_snapshot/loader.cc
+++ b/sky/tools/sky_snapshot/loader.cc
@@ -69,16 +69,16 @@
   packages_ = base::MakeAbsoluteFilePath(packages);
   dependencies_.insert(packages_.AsUTF8Unsafe());
   std::string packages_source;
-  if (!base::ReadFileToString(packages, &packages_source)) {
+  if (!base::ReadFileToString(packages_, &packages_source)) {
     fprintf(stderr, "error: Unable to load .packages file '%s'.\n",
-        packages.AsUTF8Unsafe().c_str());
+        packages_.AsUTF8Unsafe().c_str());
     exit(1);
   }
   packages_map_.reset(new tonic::PackagesMap());
   std::string error;
   if (!packages_map_->Parse(packages_source, &error)) {
     fprintf(stderr, "error: Unable to parse .packages file '%s'.\n%s\n",
-        packages.AsUTF8Unsafe().c_str(), error.c_str());
+        packages_.AsUTF8Unsafe().c_str(), error.c_str());
     exit(1);
   }
 }