Refactor how Artifacts are located (#8234)

Artifacts are now located in a central place.
This will enable us to downlaod artifacts when we need them (instead of
downloading them all upfront).
This also makes replacing sky_snapshot with gen_snapshot easier.
diff --git a/packages/flutter_tools/lib/executable.dart b/packages/flutter_tools/lib/executable.dart
index 8d4085b..af22636 100644
--- a/packages/flutter_tools/lib/executable.dart
+++ b/packages/flutter_tools/lib/executable.dart
@@ -41,6 +41,7 @@
 import 'src/commands/trace.dart';
 import 'src/commands/update_packages.dart';
 import 'src/commands/upgrade.dart';
+import 'src/artifacts.dart';
 import 'src/devfs.dart';
 import 'src/device.dart';
 import 'src/doctor.dart';
@@ -49,7 +50,6 @@
 import 'src/ios/simulators.dart';
 import 'src/run_hot.dart';
 import 'src/runner/flutter_command_runner.dart';
-import 'src/toolchain.dart';
 import 'src/usage.dart';
 
 
@@ -113,7 +113,7 @@
     context.putIfAbsent(Doctor, () => new Doctor());
     context.putIfAbsent(HotRunnerConfig, () => new HotRunnerConfig());
     context.putIfAbsent(Cache, () => new Cache());
-    context.putIfAbsent(ToolConfiguration, () => new ToolConfiguration());
+    context.putIfAbsent(Artifacts, () => new CachedArtifacts());
     context.putIfAbsent(Config, () => new Config());
     context.putIfAbsent(OperatingSystemUtils, () => new OperatingSystemUtils());
     context.putIfAbsent(Xcode, () => new Xcode());
diff --git a/packages/flutter_tools/lib/src/artifacts.dart b/packages/flutter_tools/lib/src/artifacts.dart
new file mode 100644
index 0000000..0aa9ddc
--- /dev/null
+++ b/packages/flutter_tools/lib/src/artifacts.dart
@@ -0,0 +1,264 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'base/context.dart';
+import 'base/file_system.dart';
+import 'base/platform.dart';
+import 'build_info.dart';
+import 'globals.dart';
+
+enum Artifact {
+  chromiumDebugKeyStore,
+  classesDexJar,
+  icudtlDat,
+  libskyShellSo,
+  dartIoEntriesTxt,
+  dartVmEntryPointsTxt,
+  dartVmEntryPointsAndroidTxt,
+  genSnapshot,
+  skyShell,
+  skySnapshot,
+  snapshotDart,
+  flutterFramework,
+}
+
+String _artifactToFileName(Artifact artifact) {
+  switch (artifact) {
+    case Artifact.chromiumDebugKeyStore:
+      return 'chromium-debug.keystore';
+    case Artifact.classesDexJar:
+      return 'classes.dex.jar';
+    case Artifact.icudtlDat:
+      return 'icudtl.dat';
+    case Artifact.libskyShellSo:
+      return 'libsky_shell.so';
+    case Artifact.dartIoEntriesTxt:
+      return 'dart_io_entries.txt';
+    case Artifact.dartVmEntryPointsTxt:
+      return 'dart_vm_entry_points.txt';
+    case Artifact.dartVmEntryPointsAndroidTxt:
+      return 'dart_vm_entry_points_android.txt';
+    case Artifact.genSnapshot:
+      return 'gen_snapshot';
+    case Artifact.skySnapshot:
+      return 'sky_snapshot';
+    case Artifact.skyShell:
+      return 'sky_shell';
+    case Artifact.snapshotDart:
+      return 'snapshot.dart';
+    case Artifact.flutterFramework:
+      return 'Flutter.framework';
+  }
+  assert(false, 'Invalid artifact $artifact.');
+  return null;
+}
+
+// Manages the engine artifacts of Flutter.
+abstract class Artifacts {
+  static Artifacts get instance => context[Artifacts];
+
+  static void useLocalEngine(String engineSrcPath, String engineOutPath) {
+    context.setVariable(Artifacts, new LocalEngineArtifacts(engineSrcPath, engineOutPath));
+  }
+
+  // Returns the requested [artifact] for the [platform] and [mode] combination.
+  String getArtifactPath(Artifact artifact, [TargetPlatform platform, BuildMode mode]);
+
+  // Returns which set of engine artifacts is currently used for the [platform]
+  // and [mode] combination.
+  String getEngineType(TargetPlatform platform, [BuildMode mode]);
+}
+
+/// Manages the engine artifacts downloaded to the local cache.
+class CachedArtifacts extends Artifacts {
+
+  @override
+  String getArtifactPath(Artifact artifact, [TargetPlatform platform, BuildMode mode]) {
+    platform ??= _currentHostPlatform;
+    switch (platform) {
+      case TargetPlatform.android_arm:
+      case TargetPlatform.android_x64:
+      case TargetPlatform.android_x86:
+        return _getAndroidArtifactPath(artifact, platform, mode);
+      case TargetPlatform.ios:
+        return _getIosArtifactPath(artifact, platform, mode);
+      case TargetPlatform.darwin_x64:
+      case TargetPlatform.linux_x64:
+        assert(mode == null, 'Platform $platform does not support different build modes.');
+        return _getHostArtifactPath(artifact, platform);
+    }
+    assert(false, 'Invalid platform $platform.');
+    return null;
+  }
+
+  @override
+  String getEngineType(TargetPlatform platform, [BuildMode mode]){
+    return fs.path.basename(_getEngineArtifactsPath(platform, mode));
+  }
+
+  String _getAndroidArtifactPath(Artifact artifact, TargetPlatform platform, BuildMode mode) {
+    String engineDir = _getEngineArtifactsPath(platform, mode);
+    switch (artifact) {
+      case Artifact.chromiumDebugKeyStore:
+      case Artifact.classesDexJar:
+      case Artifact.icudtlDat:
+      case Artifact.libskyShellSo:
+        return fs.path.join(engineDir, _artifactToFileName(artifact));
+      case Artifact.dartIoEntriesTxt:
+      case Artifact.dartVmEntryPointsTxt:
+      case Artifact.dartVmEntryPointsAndroidTxt:
+        assert(mode != BuildMode.debug, 'Artifact $artifact only available in non-debug mode.');
+        return fs.path.join(engineDir, _artifactToFileName(artifact));
+      case Artifact.genSnapshot:
+        assert(mode != BuildMode.debug, 'Artifact $artifact only available in non-debug mode.');
+        String hostPlatform = getNameForHostPlatform(getCurrentHostPlatform());
+        return fs.path.join(engineDir, hostPlatform, _artifactToFileName(artifact));
+      default:
+        assert(false, 'Artifact $artifact not available for platform $platform.');
+        return null;
+    }
+  }
+
+  String _getIosArtifactPath(Artifact artifact, TargetPlatform platform, BuildMode mode) {
+    String engineDir = _getEngineArtifactsPath(platform, mode);
+    switch (artifact) {
+      case Artifact.dartIoEntriesTxt:
+      case Artifact.dartVmEntryPointsTxt:
+      case Artifact.genSnapshot:
+      case Artifact.snapshotDart:
+      case Artifact.flutterFramework:
+        return fs.path.join(engineDir, _artifactToFileName(artifact));
+      default:
+        assert(false, 'Artifact $artifact not available for platform $platform.');
+        return null;
+    }
+  }
+
+  String _getHostArtifactPath(Artifact artifact, TargetPlatform platform) {
+    switch (artifact) {
+      case Artifact.skyShell:
+      case Artifact.skySnapshot:
+      case Artifact.icudtlDat:
+        String engineArtifactsPath = cache.getArtifactDirectory('engine').path;
+        String platformDirName = getNameForTargetPlatform(platform);
+        return fs.path.join(engineArtifactsPath, platformDirName, _artifactToFileName(artifact));
+      default:
+        assert(false, 'Artifact $artifact not available for platform $platform.');
+        return null;
+    }
+  }
+
+  String _getEngineArtifactsPath(TargetPlatform platform, [BuildMode mode]) {
+    String engineDir = cache.getArtifactDirectory('engine').path;
+    String platformName = getNameForTargetPlatform(platform);
+    switch (platform) {
+      case TargetPlatform.linux_x64:
+      case TargetPlatform.darwin_x64:
+        assert(mode == null, 'Platform $platform does not support different build modes.');
+        return fs.path.join(engineDir, platformName);
+      case TargetPlatform.ios:
+      case TargetPlatform.android_arm:
+      case TargetPlatform.android_x64:
+      case TargetPlatform.android_x86:
+        assert(mode != null, 'Need to specify a build mode for platform $platform.');
+        String suffix = mode != BuildMode.debug ? '-${getModeName(mode)}' : '';
+        return fs.path.join(engineDir, platformName + suffix);
+    }
+    assert(false, 'Invalid platform $platform.');
+    return null;
+  }
+
+  TargetPlatform get _currentHostPlatform {
+    if (platform.isMacOS)
+      return TargetPlatform.darwin_x64;
+    if (platform.isLinux)
+      return TargetPlatform.linux_x64;
+    throw new UnimplementedError('Host OS not supported.');
+  }
+}
+
+/// Manages the artifacts of a locally built engine.
+class LocalEngineArtifacts extends Artifacts {
+  final String _engineSrcPath;
+  final String engineOutPath; // TODO(goderbauer): This should be private.
+
+  LocalEngineArtifacts(this._engineSrcPath, this.engineOutPath);
+
+  @override
+  String getArtifactPath(Artifact artifact, [TargetPlatform platform, BuildMode mode]) {
+    switch (artifact) {
+      case Artifact.chromiumDebugKeyStore:
+        return fs.path.join(_engineSrcPath, 'build', 'android', 'ant', _artifactToFileName(artifact));
+      case Artifact.dartIoEntriesTxt:
+        return fs.path.join(_engineSrcPath, 'dart', 'runtime', 'bin', _artifactToFileName(artifact));
+      case Artifact.dartVmEntryPointsTxt:
+        return fs.path.join(_engineSrcPath, 'flutter', 'runtime', _artifactToFileName(artifact));
+      case Artifact.dartVmEntryPointsAndroidTxt:
+        return fs.path.join(_engineSrcPath, 'flutter', 'runtime', _artifactToFileName(artifact));
+      case Artifact.snapshotDart:
+        return fs.path.join(_engineSrcPath, 'flutter', 'lib', 'snapshot', _artifactToFileName(artifact));
+      case Artifact.classesDexJar:
+        return fs.path.join(engineOutPath, 'gen', 'flutter', 'shell', 'platform', 'android', 'android', _artifactToFileName(artifact));
+      case Artifact.icudtlDat:
+        return fs.path.join(engineOutPath, _artifactToFileName(artifact));
+      case Artifact.libskyShellSo:
+        String abi = _getAbiDirectory(platform);
+        return fs.path.join(engineOutPath, 'gen', 'flutter', 'shell', 'platform', 'android', 'android', fs.path.join('android', 'libs', abi, _artifactToFileName(artifact)));
+      case Artifact.genSnapshot:
+        return _genSnapshotPath(platform);
+      case Artifact.skyShell:
+        return _skyShellPath(platform);
+      case Artifact.skySnapshot:
+        return _skySnapshotPath();
+      case Artifact.flutterFramework:
+        return fs.path.join(engineOutPath, _artifactToFileName(artifact));
+    }
+    assert(false, 'Invalid artifact $artifact.');
+    return null;
+  }
+
+  @override
+  String getEngineType(TargetPlatform platform, [BuildMode mode]) {
+    return fs.path.basename(engineOutPath);
+  }
+
+  String _genSnapshotPath(TargetPlatform platform) {
+    String clang;
+    if (platform == TargetPlatform.ios) {
+      clang = 'clang_x64';
+    } else {
+      clang = getCurrentHostPlatform() == HostPlatform.darwin_x64 ? 'clang_i386' : 'clang_x86';
+    }
+    return fs.path.join(engineOutPath, clang, _artifactToFileName(Artifact.genSnapshot));
+  }
+
+  String _skySnapshotPath() {
+    String clangPath = fs.path.join(engineOutPath, 'clang_x64', _artifactToFileName(Artifact.skySnapshot));
+    if (fs.isFileSync(clangPath))
+      return clangPath;
+    return fs.path.join(engineOutPath, _artifactToFileName(Artifact.skySnapshot));
+  }
+
+  String _skyShellPath(TargetPlatform platform) {
+    if (getCurrentHostPlatform() == HostPlatform.linux_x64) {
+      return fs.path.join(engineOutPath, _artifactToFileName(Artifact.skyShell));
+    } else if (getCurrentHostPlatform() == HostPlatform.darwin_x64) {
+      return fs.path.join(engineOutPath, 'SkyShell.app', 'Contents', 'MacOS', 'SkyShell');
+    }
+    throw new Exception('Unsupported platform $platform.');
+  }
+
+  String _getAbiDirectory(TargetPlatform platform) {
+    switch (platform) {
+      case TargetPlatform.android_arm:
+        return 'armeabi-v7a';
+      case TargetPlatform.android_x64:
+        return 'x86_64';
+      case TargetPlatform.android_x86:
+        return 'x86';
+      default:
+        throw new Exception('Unsupported platform $platform.');
+    }
+  }
+}
diff --git a/packages/flutter_tools/lib/src/commands/build_aot.dart b/packages/flutter_tools/lib/src/commands/build_aot.dart
index 368a563..27529c2 100644
--- a/packages/flutter_tools/lib/src/commands/build_aot.dart
+++ b/packages/flutter_tools/lib/src/commands/build_aot.dart
@@ -4,6 +4,7 @@
 
 import 'dart:async';
 
+import '../artifacts.dart';
 import '../base/common.dart';
 import '../base/file_system.dart';
 import '../base/logger.dart';
@@ -49,7 +50,7 @@
     if (platform == null)
       throwToolExit('Unknown platform: $targetPlatform');
 
-    String typeName = fs.path.basename(tools.getEngineArtifactsDirectory(platform, getBuildMode()).path);
+    String typeName = artifacts.getEngineType(platform, getBuildMode());
     Status status = logger.startProgress('Building AOT snapshot in ${getModeName(getBuildMode())} mode ($typeName)...',
         expectSlowOperation: true);
     String outputPath = await buildAotSnapshot(
@@ -115,33 +116,7 @@
     return null;
   }
 
-  String entryPointsDir, dartEntryPointsDir, snapshotterDir, genSnapshot;
-
-  String engineSrc = tools.engineSrcPath;
-  String genSnapshotExecutable = 'gen_snapshot';
-  if (engineSrc != null) {
-    entryPointsDir  = fs.path.join(engineSrc, 'flutter', 'runtime');
-    dartEntryPointsDir = fs.path.join(engineSrc, 'dart', 'runtime', 'bin');
-    snapshotterDir = fs.path.join(engineSrc, 'flutter', 'lib', 'snapshot');
-    String engineOut = tools.getEngineArtifactsDirectory(platform, buildMode).path;
-    if (platform == TargetPlatform.ios) {
-      genSnapshot = fs.path.join(engineOut, 'clang_x64', genSnapshotExecutable);
-    } else {
-      String host32BitToolchain = getCurrentHostPlatform() == HostPlatform.darwin_x64 ? 'clang_i386' : 'clang_x86';
-      genSnapshot = fs.path.join(engineOut, host32BitToolchain, genSnapshotExecutable);
-    }
-  } else {
-    String artifactsDir = tools.getEngineArtifactsDirectory(platform, buildMode).path;
-    entryPointsDir = artifactsDir;
-    dartEntryPointsDir = entryPointsDir;
-    snapshotterDir = entryPointsDir;
-    if (platform == TargetPlatform.ios) {
-      genSnapshot = fs.path.join(artifactsDir, genSnapshotExecutable);
-    } else {
-      String hostToolsDir = fs.path.join(artifactsDir, getNameForHostPlatform(getCurrentHostPlatform()));
-      genSnapshot = fs.path.join(hostToolsDir, genSnapshotExecutable);
-    }
-  }
+  String genSnapshot = artifacts.getArtifactPath(Artifact.genSnapshot, platform, buildMode);
 
   Directory outputDir = fs.directory(outputPath);
   outputDir.createSync(recursive: true);
@@ -150,8 +125,8 @@
   String isolateSnapshotData = fs.path.join(outputDir.path, 'isolate_snapshot_data');
   String isolateSnapshotInstructions = fs.path.join(outputDir.path, 'isolate_snapshot_instr');
 
-  String vmEntryPoints = fs.path.join(entryPointsDir, 'dart_vm_entry_points.txt');
-  String ioEntryPoints = fs.path.join(dartEntryPointsDir, 'dart_io_entries.txt');
+  String vmEntryPoints = artifacts.getArtifactPath(Artifact.dartVmEntryPointsTxt, platform, buildMode);
+  String ioEntryPoints = artifacts.getArtifactPath(Artifact.dartIoEntriesTxt, platform, buildMode);
 
   PackageMap packageMap = new PackageMap(PackageMap.globalPackagesPath);
   String packageMapError = packageMap.checkValid();
@@ -184,13 +159,13 @@
     case TargetPlatform.android_arm:
     case TargetPlatform.android_x64:
     case TargetPlatform.android_x86:
-      vmEntryPointsAndroid = fs.path.join(entryPointsDir, 'dart_vm_entry_points_android.txt');
+      vmEntryPointsAndroid = artifacts.getArtifactPath(Artifact.dartVmEntryPointsAndroidTxt, platform, buildMode);
       filePaths.addAll(<String>[
         vmEntryPointsAndroid,
       ]);
       break;
     case TargetPlatform.ios:
-      snapshotDartIOS = fs.path.join(snapshotterDir, 'snapshot.dart');
+      snapshotDartIOS = artifacts.getArtifactPath(Artifact.snapshotDart, platform, buildMode);
       assembly = fs.path.join(outputDir.path, 'snapshot_assembly.S');
       filePaths.addAll(<String>[
         snapshotDartIOS,
diff --git a/packages/flutter_tools/lib/src/commands/build_apk.dart b/packages/flutter_tools/lib/src/commands/build_apk.dart
index 8f64bea..f87592e 100644
--- a/packages/flutter_tools/lib/src/commands/build_apk.dart
+++ b/packages/flutter_tools/lib/src/commands/build_apk.dart
@@ -7,6 +7,7 @@
 
 import '../android/android_sdk.dart';
 import '../android/gradle.dart';
+import '../artifacts.dart';
 import '../base/common.dart';
 import '../base/file_system.dart';
 import '../base/logger.dart';
@@ -285,28 +286,12 @@
   components.manifest = fs.file(manifest);
   components.resources = resources == null ? null : fs.directory(resources);
   components.extraFiles = extraFiles != null ? extraFiles : <String, File>{};
-
-  if (tools.isLocalEngine) {
-    String abiDir = getAbiDirectory(platform);
-    String enginePath = tools.engineSrcPath;
-    String buildDir = tools.getEngineArtifactsDirectory(platform, buildMode).path;
-
-    components.icuData = fs.file('$enginePath/third_party/icu/android/icudtl.dat');
-    components.jars = <File>[
-      fs.file('$buildDir/gen/flutter/shell/platform/android/android/classes.dex.jar')
-    ];
-    components.libSkyShell = fs.file('$buildDir/gen/flutter/shell/platform/android/android/android/libs/$abiDir/libsky_shell.so');
-    components.debugKeystore = fs.file('$enginePath/build/android/ant/chromium-debug.keystore');
-  } else {
-    Directory artifacts = tools.getEngineArtifactsDirectory(platform, buildMode);
-
-    components.icuData = fs.file(fs.path.join(artifacts.path, 'icudtl.dat'));
-    components.jars = <File>[
-      fs.file(fs.path.join(artifacts.path, 'classes.dex.jar'))
-    ];
-    components.libSkyShell = fs.file(fs.path.join(artifacts.path, 'libsky_shell.so'));
-    components.debugKeystore = fs.file(fs.path.join(artifacts.path, 'chromium-debug.keystore'));
-  }
+  components.icuData = fs.file(artifacts.getArtifactPath(Artifact.icudtlDat, platform, buildMode));
+  components.jars = <File>[
+    fs.file(artifacts.getArtifactPath(Artifact.classesDexJar, platform, buildMode))
+  ];
+  components.libSkyShell = fs.file(artifacts.getArtifactPath(Artifact.libskyShellSo, platform, buildMode));
+  components.debugKeystore = fs.file(artifacts.getArtifactPath(Artifact.chromiumDebugKeyStore, platform, buildMode));
 
   await parseServiceConfigs(components.services, jars: components.jars);
 
@@ -534,8 +519,8 @@
   if (components == null)
     throwToolExit('Failure building APK: unable to find components.');
 
-  String typeName = fs.path.basename(tools.getEngineArtifactsDirectory(platform, buildMode).path);
-  Status status = logger.startProgress('Building APK in ${getModeName(buildMode)} mode ($typeName)...',
+  String typeName = artifacts.getEngineType(platform, buildMode);
+  Status status = logger.startProgress('Building APK in ${getModeName(buildMode)} mode  ($typeName)...',
       expectSlowOperation: true);
 
   if (flxPath != null && flxPath.isNotEmpty) {
@@ -658,8 +643,10 @@
 
 String _getTargetBuildTypeToken(TargetPlatform platform, BuildMode buildMode, File outputBinary) {
   String buildType = getNameForTargetPlatform(platform) + '-' + getModeName(buildMode);
-  if (tools.isLocalEngine)
-    buildType += ' [${tools.engineBuildPath}]';
+  if (artifacts is LocalEngineArtifacts) {
+    LocalEngineArtifacts localEngineArtifacts = artifacts;
+    buildType += ' [${localEngineArtifacts.engineOutPath}]';
+  }
   if (outputBinary.existsSync())
     buildType += ' [${outputBinary.lastModifiedSync().millisecondsSinceEpoch}]';
   return buildType;
diff --git a/packages/flutter_tools/lib/src/commands/build_ios.dart b/packages/flutter_tools/lib/src/commands/build_ios.dart
index 5e3fc80..34adb0b 100644
--- a/packages/flutter_tools/lib/src/commands/build_ios.dart
+++ b/packages/flutter_tools/lib/src/commands/build_ios.dart
@@ -6,7 +6,6 @@
 
 import '../application_package.dart';
 import '../base/common.dart';
-import '../base/file_system.dart';
 import '../base/logger.dart';
 import '../base/utils.dart';
 import '../build_info.dart';
@@ -63,7 +62,7 @@
 
     String logTarget = forSimulator ? 'simulator' : 'device';
 
-    String typeName = fs.path.basename(tools.getEngineArtifactsDirectory(TargetPlatform.ios, getBuildMode()).path);
+    String typeName = artifacts.getEngineType(TargetPlatform.ios, getBuildMode());
     Status status = logger.startProgress('Building $app for $logTarget ($typeName)...',
         expectSlowOperation: true);
     XcodeBuildResult result = await buildXcodeProject(
diff --git a/packages/flutter_tools/lib/src/commands/test.dart b/packages/flutter_tools/lib/src/commands/test.dart
index ef9b7e3..292bbc2 100644
--- a/packages/flutter_tools/lib/src/commands/test.dart
+++ b/packages/flutter_tools/lib/src/commands/test.dart
@@ -6,11 +6,11 @@
 
 import 'package:test/src/executable.dart' as test; // ignore: implementation_imports
 
+import '../artifacts.dart';
 import '../base/common.dart';
 import '../base/file_system.dart';
 import '../base/io.dart';
 import '../base/logger.dart';
-import '../base/platform.dart';
 import '../base/process_manager.dart';
 import '../base/os.dart';
 import '../cache.dart';
@@ -19,7 +19,6 @@
 import '../runner/flutter_command.dart';
 import '../test/coverage_collector.dart';
 import '../test/flutter_platform.dart' as loader;
-import '../toolchain.dart';
 
 class TestCommand extends FlutterCommand {
   TestCommand() {
@@ -190,7 +189,7 @@
     }
     testArgs.addAll(files);
 
-    final String shellPath = tools.getHostToolPath(HostTool.SkyShell) ?? platform.environment['SKY_SHELL'];
+    final String shellPath = artifacts.getArtifactPath(Artifact.skyShell);
     if (!fs.isFileSync(shellPath))
       throwToolExit('Cannot find Flutter shell at $shellPath');
     loader.installHook(
diff --git a/packages/flutter_tools/lib/src/dart/dependencies.dart b/packages/flutter_tools/lib/src/dart/dependencies.dart
index 5fb22c33..6d55526 100644
--- a/packages/flutter_tools/lib/src/dart/dependencies.dart
+++ b/packages/flutter_tools/lib/src/dart/dependencies.dart
@@ -4,9 +4,9 @@
 
 import 'dart:convert';
 
+import '../artifacts.dart';
 import '../base/file_system.dart';
 import '../base/process.dart';
-import '../toolchain.dart';
 
 class DartDependencySetBuilder {
   DartDependencySetBuilder(this.mainScriptPath,
@@ -19,7 +19,7 @@
 
   Set<String> build() {
     final String skySnapshotPath =
-        ToolConfiguration.instance.getHostToolPath(HostTool.SkySnapshot);
+        Artifacts.instance.getArtifactPath(Artifact.skySnapshot);
 
     final List<String> args = <String>[
       skySnapshotPath,
diff --git a/packages/flutter_tools/lib/src/flx.dart b/packages/flutter_tools/lib/src/flx.dart
index 73b3fee..8f2bdf8 100644
--- a/packages/flutter_tools/lib/src/flx.dart
+++ b/packages/flutter_tools/lib/src/flx.dart
@@ -4,6 +4,7 @@
 
 import 'dart:async';
 
+import 'artifacts.dart';
 import 'asset.dart';
 import 'base/common.dart';
 import 'base/file_system.dart';
@@ -12,7 +13,6 @@
 import 'devfs.dart';
 import 'build_info.dart';
 import 'globals.dart';
-import 'toolchain.dart';
 import 'zip.dart';
 
 const String defaultMainPath = 'lib/main.dart';
@@ -88,7 +88,7 @@
   bool includeRobotoFonts: true,
   bool reportLicensedPackages: false
 }) async {
-  snapshotterPath ??= tools.getHostToolPath(HostTool.SkySnapshot);
+  snapshotterPath ??= artifacts.getArtifactPath(Artifact.skySnapshot);
   outputPath ??= defaultFlxOutputPath;
   kernelPath ??= defaultKernelPath;
   snapshotPath ??= defaultSnapshotPath;
diff --git a/packages/flutter_tools/lib/src/globals.dart b/packages/flutter_tools/lib/src/globals.dart
index f177ebb..3334798 100644
--- a/packages/flutter_tools/lib/src/globals.dart
+++ b/packages/flutter_tools/lib/src/globals.dart
@@ -2,16 +2,16 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+import 'artifacts.dart';
 import 'base/config.dart';
 import 'base/context.dart';
 import 'base/logger.dart';
 import 'cache.dart';
-import 'toolchain.dart';
 
 Logger get logger => context[Logger];
 Cache get cache => Cache.instance;
 Config get config => Config.instance;
-ToolConfiguration get tools => ToolConfiguration.instance;
+Artifacts get artifacts => Artifacts.instance;
 
 /// Display an error level message to the user. Commands should use this if they
 /// fail in some way.
diff --git a/packages/flutter_tools/lib/src/ios/xcodeproj.dart b/packages/flutter_tools/lib/src/ios/xcodeproj.dart
index 245b335..c7bb1d9 100644
--- a/packages/flutter_tools/lib/src/ios/xcodeproj.dart
+++ b/packages/flutter_tools/lib/src/ios/xcodeproj.dart
@@ -5,6 +5,7 @@
 import '../base/file_system.dart';
 import '../base/process.dart';
 import '../build_info.dart';
+import '../artifacts.dart';
 import '../cache.dart';
 import '../globals.dart';
 
@@ -33,11 +34,13 @@
 
   localsBuffer.writeln('SYMROOT=\${SOURCE_ROOT}/../${getIosBuildDirectory()}');
 
-  String flutterFrameworkDir = fs.path.normalize(tools.getEngineArtifactsDirectory(TargetPlatform.ios, mode).path);
+  String flutterFrameworkDir = fs.path.normalize(fs.path.dirname(artifacts.getArtifactPath(Artifact.flutterFramework, TargetPlatform.ios, mode)));
   localsBuffer.writeln('FLUTTER_FRAMEWORK_DIR=$flutterFrameworkDir');
 
-  if (tools.isLocalEngine)
-    localsBuffer.writeln('LOCAL_ENGINE=${tools.engineBuildPath}');
+  if (artifacts is LocalEngineArtifacts) {
+    LocalEngineArtifacts localEngineArtifacts = artifacts;
+    localsBuffer.writeln('LOCAL_ENGINE=${localEngineArtifacts.engineOutPath}');
+  }
 
   File localsFile = fs.file(fs.path.join(projectPath, 'ios', 'Flutter', 'Generated.xcconfig'));
   localsFile.createSync(recursive: true);
diff --git a/packages/flutter_tools/lib/src/runner/flutter_command_runner.dart b/packages/flutter_tools/lib/src/runner/flutter_command_runner.dart
index 1eeea2c..5cb6ca9 100644
--- a/packages/flutter_tools/lib/src/runner/flutter_command_runner.dart
+++ b/packages/flutter_tools/lib/src/runner/flutter_command_runner.dart
@@ -8,6 +8,7 @@
 import 'package:args/command_runner.dart';
 
 import '../android/android_sdk.dart';
+import '../artifacts.dart';
 import '../base/common.dart';
 import '../base/context.dart';
 import '../base/file_system.dart';
@@ -19,7 +20,6 @@
 import '../dart/package_map.dart';
 import '../device.dart';
 import '../globals.dart';
-import '../toolchain.dart';
 import '../usage.dart';
 import '../version.dart';
 
@@ -206,8 +206,7 @@
     // Set up the tooling configuration.
     String enginePath = _findEnginePath(globalResults);
     if (enginePath != null) {
-      ToolConfiguration.instance.engineSrcPath = enginePath;
-      ToolConfiguration.instance.engineBuildPath = _findEngineBuildPath(globalResults, enginePath);
+      Artifacts.useLocalEngine(enginePath, _findEngineBuildPath(globalResults, enginePath));
     }
 
     // The Android SDK could already have been set by tests.
diff --git a/packages/flutter_tools/lib/src/toolchain.dart b/packages/flutter_tools/lib/src/toolchain.dart
deleted file mode 100644
index f60c6e2..0000000
--- a/packages/flutter_tools/lib/src/toolchain.dart
+++ /dev/null
@@ -1,83 +0,0 @@
-// Copyright 2015 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-import 'base/context.dart';
-import 'base/file_system.dart';
-import 'build_info.dart';
-import 'cache.dart';
-import 'globals.dart';
-
-enum HostTool {
-  SkySnapshot,
-  SkyShell,
-}
-
-const Map<HostTool, String> _kHostToolFileName = const <HostTool, String>{
-  HostTool.SkySnapshot: 'sky_snapshot',
-  HostTool.SkyShell: 'sky_shell',
-};
-
-/// A ToolConfiguration can return the tools directory for the current host platform
-/// and the engine artifact directory for a given target platform. It is configurable
-/// via command-line arguments in order to support local engine builds.
-class ToolConfiguration {
-  ToolConfiguration();
-
-  Cache get cache => context[Cache];
-
-  static ToolConfiguration get instance => context[ToolConfiguration];
-
-  /// Override using the artifacts from the cache directory (--engine-src-path).
-  String engineSrcPath;
-
-  /// Path to a local engine build acting as a source for artifacts (--local-engine).
-  String engineBuildPath;
-
-  bool get isLocalEngine => engineSrcPath != null;
-
-  /// Return the directory that contains engine artifacts for the given targets.
-  /// This directory might contain artifacts like `libsky_shell.so`.
-  Directory getEngineArtifactsDirectory(TargetPlatform platform, BuildMode mode) {
-    Directory dir = _getEngineArtifactsDirectory(platform, mode);
-    if (dir != null)
-      printTrace('Using engine artifacts dir: ${dir.path}');
-    return dir;
-  }
-
-  Directory _getEngineArtifactsDirectory(TargetPlatform platform, BuildMode mode) {
-    if (engineBuildPath != null) {
-      return fs.directory(engineBuildPath);
-    } else {
-      String suffix = mode != BuildMode.debug ? '-${getModeName(mode)}' : '';
-
-      // Create something like `android-arm` or `android-arm-release`.
-      String dirName = getNameForTargetPlatform(platform) + suffix;
-      Directory engineDir = cache.getArtifactDirectory('engine');
-      return fs.directory(fs.path.join(engineDir.path, dirName));
-    }
-  }
-
-  String getHostToolPath(HostTool tool) {
-    if (engineBuildPath == null) {
-      return fs.path.join(cache.getArtifactDirectory('engine').path,
-                       getNameForHostPlatform(getCurrentHostPlatform()),
-                       _kHostToolFileName[tool]);
-    }
-
-    if (tool == HostTool.SkySnapshot) {
-      String clangPath = fs.path.join(engineBuildPath, 'clang_x64', 'sky_snapshot');
-      if (fs.isFileSync(clangPath))
-        return clangPath;
-      return fs.path.join(engineBuildPath, 'sky_snapshot');
-    } else if (tool == HostTool.SkyShell) {
-      if (getCurrentHostPlatform() == HostPlatform.linux_x64) {
-        return fs.path.join(engineBuildPath, 'sky_shell');
-      } else if (getCurrentHostPlatform() == HostPlatform.darwin_x64) {
-        return fs.path.join(engineBuildPath, 'SkyShell.app', 'Contents', 'MacOS', 'SkyShell');
-      }
-    }
-
-    throw 'Unexpected host tool: $tool';
-  }
-}
diff --git a/packages/flutter_tools/test/all.dart b/packages/flutter_tools/test/all.dart
index b07cb50..be37b4c 100644
--- a/packages/flutter_tools/test/all.dart
+++ b/packages/flutter_tools/test/all.dart
@@ -16,6 +16,7 @@
 import 'android_device_test.dart' as android_device_test;
 import 'android_sdk_test.dart' as android_sdk_test;
 import 'application_package_test.dart' as application_package_test;
+import 'artifacts_test.dart' as artifacts_test;
 import 'asset_bundle_test.dart' as asset_bundle_test;
 import 'base_utils_test.dart' as base_utils_test;
 import 'channel_test.dart' as channel_test;
@@ -41,7 +42,6 @@
 import 'run_test.dart' as run_test;
 import 'stop_test.dart' as stop_test;
 import 'test_test.dart' as test_test;
-import 'toolchain_test.dart' as toolchain_test;
 import 'trace_test.dart' as trace_test;
 import 'upgrade_test.dart' as upgrade_test;
 import 'utils_test.dart' as utils_test;
@@ -55,6 +55,7 @@
   android_device_test.main();
   android_sdk_test.main();
   application_package_test.main();
+  artifacts_test.main();
   asset_bundle_test.main();
   base_utils_test.main();
   channel_test.main();
@@ -80,7 +81,6 @@
   run_test.main();
   stop_test.main();
   test_test.main();
-  toolchain_test.main();
   trace_test.main();
   upgrade_test.main();
   utils_test.main();
diff --git a/packages/flutter_tools/test/artifacts_test.dart b/packages/flutter_tools/test/artifacts_test.dart
new file mode 100644
index 0000000..ad31621
--- /dev/null
+++ b/packages/flutter_tools/test/artifacts_test.dart
@@ -0,0 +1,118 @@
+// Copyright 2017 The Chromium Authors. 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:flutter_tools/src/base/file_system.dart';
+import 'package:flutter_tools/src/base/platform.dart';
+import 'package:flutter_tools/src/build_info.dart';
+import 'package:flutter_tools/src/cache.dart';
+import 'package:flutter_tools/src/artifacts.dart';
+import 'package:test/test.dart';
+
+import 'src/context.dart';
+
+void main() {
+  group('CachedArtifacts', () {
+
+    Directory tempDir;
+    CachedArtifacts artifacts;
+
+    setUp(() {
+      tempDir = fs.systemTempDirectory.createTempSync('flutter_temp');
+      artifacts = new CachedArtifacts();
+    });
+
+    tearDown(() {
+      tempDir.deleteSync(recursive: true);
+    });
+
+    testUsingContext('getArtifactPath', () {
+      expect(
+          artifacts.getArtifactPath(Artifact.libskyShellSo, TargetPlatform.android_arm, BuildMode.debug),
+          fs.path.join(tempDir.path, 'bin', 'cache', 'artifacts', 'engine', 'android-arm', 'libsky_shell.so')
+      );
+      expect(
+          artifacts.getArtifactPath(Artifact.flutterFramework, TargetPlatform.ios, BuildMode.release),
+          fs.path.join(tempDir.path, 'bin', 'cache', 'artifacts', 'engine', 'ios-release', 'Flutter.framework')
+      );
+
+      expect(
+          artifacts.getArtifactPath(Artifact.skyShell),
+          fs.path.join(tempDir.path, 'bin', 'cache', 'artifacts', 'engine', 'linux-x64', 'sky_shell')
+      );
+    }, overrides: <Type, Generator> {
+      Cache: () => new Cache(rootOverride: tempDir),
+      Platform: () => new FakePlatform(operatingSystem: 'linux')
+    });
+
+    testUsingContext('getEngineType', () {
+      expect(
+          artifacts.getEngineType(TargetPlatform.android_arm, BuildMode.debug),
+          'android-arm'
+      );
+      expect(
+          artifacts.getEngineType(TargetPlatform.ios, BuildMode.release),
+          'ios-release'
+      );
+
+      expect(
+          artifacts.getEngineType(TargetPlatform.darwin_x64),
+          'darwin-x64'
+      );
+    }, overrides: <Type, Generator> {
+      Cache: () => new Cache(rootOverride: tempDir),
+      Platform: () => new FakePlatform(operatingSystem: 'linux')
+    });
+  });
+
+  group('LocalEngineArtifacts', () {
+
+    Directory tempDir;
+    LocalEngineArtifacts artifacts;
+
+    setUp(() {
+      tempDir = fs.systemTempDirectory.createTempSync('flutter_temp');
+      artifacts = new LocalEngineArtifacts(tempDir.path, fs.path.join(tempDir.path, 'out', 'android_debug_unopt'));
+    });
+
+    tearDown(() {
+      tempDir.deleteSync(recursive: true);
+    });
+
+    testUsingContext('getArtifactPath', () {
+      expect(
+          artifacts.getArtifactPath(Artifact.dartIoEntriesTxt, TargetPlatform.android_arm, BuildMode.debug),
+          fs.path.join(tempDir.path, 'dart', 'runtime', 'bin', 'dart_io_entries.txt')
+      );
+      expect(
+          artifacts.getArtifactPath(Artifact.flutterFramework, TargetPlatform.ios, BuildMode.release),
+          fs.path.join(tempDir.path, 'out', 'android_debug_unopt', 'Flutter.framework')
+      );
+
+      expect(
+          artifacts.getArtifactPath(Artifact.skyShell),
+          fs.path.join(tempDir.path, 'out', 'android_debug_unopt', 'sky_shell')
+      );
+    }, overrides: <Type, Generator> {
+      Platform: () => new FakePlatform(operatingSystem: 'linux')
+    });
+
+    testUsingContext('getEngineType', () {
+      expect(
+          artifacts.getEngineType(TargetPlatform.android_arm, BuildMode.debug),
+          'android_debug_unopt'
+      );
+      expect(
+          artifacts.getEngineType(TargetPlatform.ios, BuildMode.release),
+          'android_debug_unopt'
+      );
+
+      expect(
+          artifacts.getEngineType(TargetPlatform.darwin_x64),
+          'android_debug_unopt'
+      );
+    }, overrides: <Type, Generator> {
+      Platform: () => new FakePlatform(operatingSystem: 'linux')
+    });
+  });
+}
\ No newline at end of file
diff --git a/packages/flutter_tools/test/src/context.dart b/packages/flutter_tools/test/src/context.dart
index c6b6087..f3230e3 100644
--- a/packages/flutter_tools/test/src/context.dart
+++ b/packages/flutter_tools/test/src/context.dart
@@ -4,6 +4,7 @@
 
 import 'dart:async';
 
+import 'package:flutter_tools/src/artifacts.dart';
 import 'package:flutter_tools/src/base/config.dart';
 import 'package:flutter_tools/src/base/context.dart';
 import 'package:flutter_tools/src/base/file_system.dart';
@@ -17,7 +18,6 @@
 import 'package:flutter_tools/src/ios/mac.dart';
 import 'package:flutter_tools/src/ios/simulators.dart';
 import 'package:flutter_tools/src/run_hot.dart';
-import 'package:flutter_tools/src/toolchain.dart';
 import 'package:flutter_tools/src/usage.dart';
 
 import 'package:mockito/mockito.dart';
@@ -53,7 +53,7 @@
     testContext.putIfAbsent(Doctor, () => new MockDoctor());
     testContext.putIfAbsent(HotRunnerConfig, () => new HotRunnerConfig());
     testContext.putIfAbsent(Cache, () => new Cache());
-    testContext.putIfAbsent(ToolConfiguration, () => new ToolConfiguration());
+    testContext.putIfAbsent(Artifacts, () => new CachedArtifacts());
     testContext.putIfAbsent(Config, () => new Config());
     testContext.putIfAbsent(OperatingSystemUtils, () {
       MockOperatingSystemUtils os = new MockOperatingSystemUtils();
diff --git a/packages/flutter_tools/test/toolchain_test.dart b/packages/flutter_tools/test/toolchain_test.dart
deleted file mode 100644
index 10fc334..0000000
--- a/packages/flutter_tools/test/toolchain_test.dart
+++ /dev/null
@@ -1,46 +0,0 @@
-// Copyright 2016 The Chromium Authors. 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:flutter_tools/src/base/file_system.dart';
-import 'package:flutter_tools/src/build_info.dart';
-import 'package:flutter_tools/src/cache.dart';
-import 'package:flutter_tools/src/toolchain.dart';
-import 'package:test/test.dart';
-
-import 'src/context.dart';
-
-void main() {
-  group('ToolConfiguration', () {
-    Directory tempDir;
-    tempDir = fs.systemTempDirectory.createTempSync('flutter_temp');
-
-    testUsingContext('using cache', () {
-      ToolConfiguration toolConfig = new ToolConfiguration();
-
-      expect(
-        toolConfig.getEngineArtifactsDirectory(TargetPlatform.android_arm, BuildMode.debug).path,
-        endsWith(fs.path.join('cache', 'artifacts', 'engine', 'android-arm'))
-      );
-      expect(
-        toolConfig.getEngineArtifactsDirectory(TargetPlatform.android_arm, BuildMode.release).path,
-        endsWith(fs.path.join('cache', 'artifacts', 'engine', 'android-arm-release'))
-      );
-      expect(tempDir, isNotNull);
-      tempDir.deleteSync(recursive: true);
-    }, overrides: <Type, Generator> {
-      Cache: () => new Cache(rootOverride: tempDir),
-    });
-
-    testUsingContext('using enginePath', () {
-      ToolConfiguration toolConfig = new ToolConfiguration();
-      toolConfig.engineSrcPath = 'engine';
-      toolConfig.engineBuildPath = 'engine/out/android_debug';
-
-      expect(
-        toolConfig.getEngineArtifactsDirectory(TargetPlatform.android_arm, BuildMode.debug).path,
-        'engine/out/android_debug'
-      );
-    });
-  });
-}