diff --git a/src/filesystem/__tests__/PhutilDeferredLogTestCase.php b/src/filesystem/__tests__/PhutilDeferredLogTestCase.php
index 481a2a4..83797be 100644
--- a/src/filesystem/__tests__/PhutilDeferredLogTestCase.php
+++ b/src/filesystem/__tests__/PhutilDeferredLogTestCase.php
@@ -1,166 +1,167 @@
 <?php
 
 final class PhutilDeferredLogTestCase extends PhutilTestCase {
 
   public function testLogging() {
     $this->checkLog(
       "derp\n",
       'derp',
       array());
 
     $this->checkLog(
       "[20 Aug 1984] alincoln\n",
       '[%T] %u',
       array(
         'T' => '20 Aug 1984',
         'u' => 'alincoln',
       ));
 
     $this->checkLog(
       "%%%%%\n",
       '%%%%%%%%%%',
       array(
         '%' => '%',
       ));
 
     $this->checkLog(
       "\\000\\001\\002\n",
       '%a%b%c',
       array(
         'a' => chr(0),
         'b' => chr(1),
         'c' => chr(2),
       ));
 
     $this->checkLog(
       "Download: 100%\n",
       'Download: %C',
       array(
         'C' => '100%',
       ));
 
     $this->checkLog(
       "- bee -\n",
       '%a %b %c',
       array(
         'b' => 'bee',
       ));
 
     $this->checkLog(
       "\\\\\n",
       '%b',
       array(
         'b' => '\\',
       ));
 
     $this->checkLog(
       "a\t\\t\n",
       "%a\t%b",
       array(
         'a' => 'a',
         'b' => "\t",
       ));
 
     $this->checkLog(
       "\1ab\n",
       "\1a%a",
       array(
         'a' => 'b',
       ));
 
     $this->checkLog(
       "a % xb\n",
       '%a %% x%b',
       array(
         'a' => 'a',
         'b' => 'b',
       ));
   }
 
   public function testLogWriteFailure() {
     $caught = null;
     try {
       if (phutil_is_hiphop_runtime()) {
         // In HipHop exceptions thrown in destructors are not normally
         // catchable, so call __destruct() explicitly.
         $log = new PhutilDeferredLog('/derp/derp/derp/derp/derp', 'derp');
         $log->__destruct();
       } else {
         new PhutilDeferredLog('/derp/derp/derp/derp/derp', 'derp');
       }
     } catch (Exception $ex) {
       $caught = $ex;
     }
     $this->assertTrue($caught instanceof Exception);
   }
 
   public function testManyWriters() {
     $root = phutil_get_library_root('phutil').'/../';
     $bin = $root.'scripts/test/deferred_log.php';
 
     $n_writers = 3;
     $n_lines   = 8;
 
     $tmp = new TempFile();
 
     $futures = array();
     for ($ii = 0; $ii < $n_writers; $ii++) {
       $futures[] = new ExecFuture('%s %d %s', $bin, $n_lines, (string)$tmp);
     }
 
-    Futures($futures)->resolveAll();
+    id(new FutureIterator($futures))
+      ->resolveAll();
 
     $this->assertEqual(
       str_repeat("abcdefghijklmnopqrstuvwxyz\n", $n_writers * $n_lines),
       Filesystem::readFile($tmp));
   }
 
   public function testNoWrite() {
     $tmp = new TempFile();
 
     $log = new PhutilDeferredLog($tmp, 'xyz');
     $log->setFile(null);
     unset($log);
 
     $this->assertEqual('', Filesystem::readFile($tmp), 'No Write');
   }
 
   public function testDoubleWrite() {
     $tmp = new TempFile();
 
     $log = new PhutilDeferredLog($tmp, 'xyz');
     $log->write();
     $log->write();
     unset($log);
 
     $this->assertEqual("xyz\n", Filesystem::readFile($tmp), 'Double Write');
   }
 
   public function testSetAfterWrite() {
     $tmp1 = new TempFile();
     $tmp2 = new TempFile();
 
     $log = new PhutilDeferredLog($tmp1, 'xyz');
     $log->write();
 
     $caught = null;
     try {
       $log->setFile($tmp2);
     } catch (Exception $ex) {
       $caught = $ex;
     }
 
     $this->assertTrue($caught instanceof Exception, 'Set After Write');
   }
 
   private function checkLog($expect, $format, $data) {
     $tmp = new TempFile();
 
     $log = new PhutilDeferredLog($tmp, $format);
     $log->setData($data);
     unset($log);
 
     $this->assertEqual($expect, Filesystem::readFile($tmp), $format);
   }
 
 }
diff --git a/src/future/FutureIterator.php b/src/future/FutureIterator.php
index 10165d8..147757f 100644
--- a/src/future/FutureIterator.php
+++ b/src/future/FutureIterator.php
@@ -1,325 +1,329 @@
 <?php
 
 /**
  * FutureIterator aggregates @{class:Future}s and allows you to respond to them
  * in the order they resolve. This is useful because it minimizes the amount of
  * time your program spends waiting on parallel processes.
  *
  *   $futures = array(
  *     'a.txt' => new ExecFuture('wc -c a.txt'),
  *     'b.txt' => new ExecFuture('wc -c b.txt'),
  *     'c.txt' => new ExecFuture('wc -c c.txt'),
  *   );
  *
- *   foreach (Futures($futures) as $key => $future) {
+ *   foreach (new FutureIterator($futures) as $key => $future) {
  *     // IMPORTANT: keys are preserved but the order of elements is not. This
  *     // construct iterates over the futures in the order they resolve, so the
  *     // fastest future is the one you'll get first. This allows you to start
  *     // doing followup processing as soon as possible.
  *
  *     list($err, $stdout) = $future->resolve();
  *     do_some_processing($stdout);
  *   }
  *
  * For a general overview of futures, see @{article:Using Futures}.
  *
  * @task  basics    Basics
  * @task  config    Configuring Iteration
  * @task  iterator  Iterator Interface
  * @task  internal  Internals
  */
 final class FutureIterator implements Iterator {
 
   protected $wait     = array();
   protected $work     = array();
   protected $futures  = array();
   protected $key;
 
   protected $limit;
 
   protected $timeout;
   protected $isTimeout = false;
 
 
 /* -(  Basics  )------------------------------------------------------------- */
 
 
   /**
    * Create a new iterator over a list of futures. By convention, use the
    * convenience function @{function:Futures} instead of instantiating this
    * class directly.
    *
    * @param list  List of @{class:Future}s to resolve.
    * @task basics
    */
   public function __construct(array $futures) {
     assert_instances_of($futures, 'Future');
     $this->futures = $futures;
   }
 
 
   /**
    * Block until all futures resolve.
    *
    * @return void
    * @task basics
    */
   public function resolveAll() {
     foreach ($this as $future) {
       $future->resolve();
     }
   }
 
   /**
    * Add another future to the set of futures. This is useful if you have a
    * set of futures to run mostly in parallel, but some futures depend on
    * others.
    *
    * @param Future  @{class:Future} to add to iterator
    * @task basics
    */
   public function addFuture(Future $future, $key = null) {
     if ($key === null) {
       $this->futures[] = $future;
       $this->wait[] = last_key($this->futures);
     } else if (!isset($this->futures[$key])) {
       $this->futures[$key] = $future;
       $this->wait[] = $key;
     } else {
       throw new Exception("Invalid key {$key}");
     }
 
     // Start running the future if we don't have $this->limit futures running
     // already. updateWorkingSet() won't start running the future if there's no
     // limit, so we'll manually poke it here in that case.
     $this->updateWorkingSet();
     if (!$this->limit) {
       $future->isReady();
     }
     return $this;
   }
 
 
 /* -(  Configuring Iteration  )---------------------------------------------- */
 
 
   /**
    * Set a maximum amount of time you want to wait before the iterator will
    * yield a result. If no future has resolved yet, the iterator will yield
    * null for key and value. Among other potential uses, you can use this to
    * show some busy indicator:
    *
-   *   foreach (Futures($futures)->setUpdateInterval(1) as $future) {
+   *   $futures = id(new FutureIterator($futures))
+   *     ->setUpdateInterval(1);
+   *   foreach ($futures as $future) {
    *     if ($future === null) {
    *       echo "Still working...\n";
    *     } else {
    *       // ...
    *     }
    *   }
    *
    * This will echo "Still working..." once per second as long as futures are
    * resolving. By default, FutureIterator never yields null.
    *
    * @param float Maximum number of seconds to block waiting on futures before
    *              yielding null.
    * @return this
    *
    * @task config
    */
   public function setUpdateInterval($interval) {
     $this->timeout = $interval;
     return $this;
   }
 
 
   /**
    * Limit the number of simultaneously executing futures.
    *
-   *  foreach (Futures($futures)->limit(4) as $future) {
+   *  $futures = id(new FutureIterator($futures))
+   *    ->limit(4);
+   *  foreach ($futures as $future) {
    *    // Run no more than 4 futures simultaneously.
    *  }
    *
    * @param int Maximum number of simultaneous jobs allowed.
    * @return this
    *
    * @task config
    */
   public function limit($max) {
     $this->limit = $max;
     return $this;
   }
 
 
 /* -(  Iterator Interface  )------------------------------------------------- */
 
 
   /**
    * @task iterator
    */
   public function rewind() {
     $this->wait = array_keys($this->futures);
     $this->work = null;
     $this->updateWorkingSet();
     $this->next();
   }
 
   /**
    * @task iterator
    */
   public function next() {
     $this->key = null;
     if (!count($this->wait)) {
       return;
     }
 
     $read_sockets = array();
     $write_sockets = array();
 
     $start = microtime(true);
     $timeout = $this->timeout;
     $this->isTimeout = false;
 
     $check = $this->getWorkingSet();
     $resolve = null;
     do {
       $read_sockets    = array();
       $write_sockets   = array();
       $can_use_sockets = true;
       $wait_time       = 1;
       foreach ($check as $wait => $key) {
         $future = $this->futures[$key];
         try {
           if ($future->getException()) {
             $resolve = $wait;
             continue;
           }
           if ($future->isReady()) {
             if ($resolve === null) {
               $resolve = $wait;
             }
             continue;
           }
 
           $got_sockets = false;
           $socks = $future->getReadSockets();
           if ($socks) {
             $got_sockets = true;
             foreach ($socks as $socket) {
               $read_sockets[] = $socket;
             }
           }
 
           $socks = $future->getWriteSockets();
           if ($socks) {
             $got_sockets = true;
             foreach ($socks as $socket) {
               $write_sockets[] = $socket;
             }
           }
 
           // If any currently active future had neither read nor write sockets,
           // we can't wait for the current batch of items using sockets.
           if (!$got_sockets) {
             $can_use_sockets = false;
           } else {
             $wait_time = min($wait_time, $future->getDefaultWait());
           }
         } catch (Exception $ex) {
           $this->futures[$key]->setException($ex);
           $resolve = $wait;
           break;
         }
       }
       if ($resolve === null) {
 
         // Check for a setUpdateInterval() timeout.
         if ($timeout !== null) {
           $elapsed = microtime(true) - $start;
           if ($elapsed > $timeout) {
             $this->isTimeout = true;
             return;
           } else {
             $wait_time = $timeout - $elapsed;
           }
         }
 
         if ($can_use_sockets) {
           Future::waitForSockets($read_sockets, $write_sockets, $wait_time);
         } else {
           usleep(1000);
         }
       }
     } while ($resolve === null);
 
     $this->key = $this->wait[$resolve];
     unset($this->wait[$resolve]);
     $this->updateWorkingSet();
   }
 
   /**
    * @task iterator
    */
   public function current() {
     if ($this->isTimeout) {
       return null;
     }
     return $this->futures[$this->key];
   }
 
   /**
    * @task iterator
    */
   public function key() {
     if ($this->isTimeout) {
       return null;
     }
     return $this->key;
   }
 
   /**
    * @task iterator
    */
   public function valid() {
     if ($this->isTimeout) {
       return true;
     }
     return ($this->key !== null);
   }
 
 
 /* -(  Internals  )---------------------------------------------------------- */
 
 
   /**
    * @task internal
    */
   protected function getWorkingSet() {
     if ($this->work === null) {
       return $this->wait;
     }
 
     return $this->work;
   }
 
   /**
    * @task internal
    */
   protected function updateWorkingSet() {
     if (!$this->limit) {
       return;
     }
 
     $old = $this->work;
     $this->work = array_slice($this->wait, 0, $this->limit, true);
 
     //  If we're using a limit, our futures are sleeping and need to be polled
     //  to begin execution, so poll any futures which weren't in our working set
     //  before.
     foreach ($this->work as $work => $key) {
       if (!isset($old[$work])) {
         $this->futures[$key]->isReady();
       }
     }
   }
 
 }
diff --git a/src/future/exec/__tests__/ExecFutureTestCase.php b/src/future/exec/__tests__/ExecFutureTestCase.php
index 70513c1..2d668a0 100644
--- a/src/future/exec/__tests__/ExecFutureTestCase.php
+++ b/src/future/exec/__tests__/ExecFutureTestCase.php
@@ -1,156 +1,156 @@
 <?php
 
 final class ExecFutureTestCase extends PhutilTestCase {
 
   public function testEmptyWrite() {
     // NOTE: This is mostly testing that we don't hang while doing an empty
     // write.
 
     list($stdout) = id(new ExecFuture('cat'))->write('')->resolvex();
 
     $this->assertEqual('', $stdout);
   }
 
   public function testKeepPipe() {
     // NOTE: This is mostly testing the semantics of $keep_pipe in write().
 
     list($stdout) = id(new ExecFuture('cat'))
       ->write('', true)
       ->start()
       ->write('x', true)
       ->write('y', true)
       ->write('z', false)
       ->resolvex();
 
     $this->assertEqual('xyz', $stdout);
   }
 
   public function testLargeBuffer() {
     // NOTE: This is mostly a coverage test to hit branches where we're still
     // flushing a buffer.
 
     $data = str_repeat('x', 1024 * 1024 * 4);
     list($stdout) = id(new ExecFuture('cat'))->write($data)->resolvex();
 
     $this->assertEqual($data, $stdout);
   }
 
   public function testBufferLimit() {
     $data = str_repeat('x', 1024 * 1024);
     list($stdout) = id(new ExecFuture('cat'))
       ->setStdoutSizeLimit(1024)
       ->write($data)
       ->resolvex();
 
     $this->assertEqual(substr($data, 0, 1024), $stdout);
   }
 
   public function testResolveTimeoutTestShouldRunLessThan1Sec() {
     // NOTE: This tests interactions between the resolve() timeout and the
     // ExecFuture timeout, which are similar but not identical.
 
     $future = id(new ExecFuture('sleep 32000'))->start();
     $future->setTimeout(32000);
 
     // We expect this to return in 0.01s.
     $result = $future->resolve(0.01);
     $this->assertEqual($result, null);
 
     // We expect this to now force the time out / kill immediately. If we don't
     // do this, we'll hang when exiting until our subprocess exits (32000
     // seconds!)
     $future->setTimeout(0.01);
     $future->resolve();
   }
 
 
   public function testTimeoutTestShouldRunLessThan1Sec() {
     // NOTE: This is partly testing that we choose appropriate select wait
     // times; this test should run for significantly less than 1 second.
 
     $future = new ExecFuture('sleep 32000');
     list($err) = $future->setTimeout(0.01)->resolve();
 
     $this->assertTrue($err > 0);
     $this->assertTrue($future->getWasKilledByTimeout());
   }
 
   public function testMultipleTimeoutsTestShouldRunLessThan1Sec() {
     $futures = array();
     for ($ii = 0; $ii < 4; $ii++) {
       $futures[] = id(new ExecFuture('sleep 32000'))->setTimeout(0.01);
     }
 
-    foreach (Futures($futures) as $future) {
+    foreach (new FutureIterator($futures) as $future) {
       list ($err) = $future->resolve();
 
       $this->assertTrue($err > 0);
       $this->assertTrue($future->getWasKilledByTimeout());
     }
   }
 
   public function testNoHangOnExecFutureDestructionWithRunningChild() {
     $start = microtime(true);
       $future = new ExecFuture('sleep 30');
       $future->start();
       unset($future);
     $end = microtime(true);
 
     // If ExecFuture::__destruct() hangs until the child closes, we won't make
     // it here in time.
     $this->assertTrue(($end - $start) < 5);
   }
 
   public function testMultipleResolves() {
     // It should be safe to call resolve(), resolvex(), resolveKill(), etc.,
     // as many times as you want on the same process.
 
     $future = new ExecFuture('echo quack');
     $future->resolve();
     $future->resolvex();
     list($err) = $future->resolveKill();
 
     $this->assertEqual(0, $err);
   }
 
   public function testReadBuffering() {
     $str_len_8 = 'abcdefgh';
     $str_len_4 = 'abcd';
 
     // This is a write/read with no read buffer.
     $future = new ExecFuture('cat');
     $future->write($str_len_8);
 
     do {
       $future->isReady();
       list($read) = $future->read();
       if (strlen($read)) {
         break;
       }
     } while (true);
 
     // We expect to get the entire string back in the read.
     $this->assertEqual($str_len_8, $read);
     $future->resolve();
 
 
     // This is a write/read with a read buffer.
     $future = new ExecFuture('cat');
     $future->write($str_len_8);
 
     // Set the read buffer size.
     $future->setReadBufferSize(4);
     do {
       $future->isReady();
       list($read) = $future->read();
       if (strlen($read)) {
         break;
       }
     } while (true);
 
     // We expect to get the entire string back in the read.
     $this->assertEqual($str_len_4, $read);
     $future->resolve();
   }
 
 }
diff --git a/src/future/query/QueryFuture.php b/src/future/query/QueryFuture.php
index 8ccc23f..9878581 100644
--- a/src/future/query/QueryFuture.php
+++ b/src/future/query/QueryFuture.php
@@ -1,129 +1,129 @@
 <?php
 
 /**
  * This class provides several approaches for querying data from the database:
  *
  *   # Async queries: Used under MySQLi with MySQLnd.
  *   # Parallel queries: Used under HPHP.
  *   # Multi queries: Used under MySQLi or HPHP.
  *   # Single queries: Used under MySQL.
  *
  * The class automatically decides which approach to use. Usage is like with
  * other futures:
  *
  *   $futures = array();
  *   $futures[] = new QueryFuture($conn1, 'SELECT 1');
  *   $futures[] = new QueryFuture($conn1, 'DELETE FROM table');
  *   $futures[] = new QueryFuture($conn2, 'SELECT 2');
  *
- *   foreach (Futures($futures) as $future) {
+ *   foreach (new FutureIterator($futures) as $future) {
  *     try {
  *       $result = $future->resolve();
  *     } catch (AphrontQueryException $ex) {
  *     }
  *   }
  *
  * `$result` contains a list of dicts for select queries or number of modified
  * rows for modification queries.
  */
 final class QueryFuture extends Future {
 
   private static $futures = array();
 
   private $conn;
   private $query;
   private $id;
   private $async;
   private $profilerCallID;
 
   public function __construct(
     AphrontDatabaseConnection $conn,
     $pattern/* , ... */) {
 
     $this->conn = $conn;
 
     $args = func_get_args();
     $args = array_slice($args, 2);
     $this->query = vqsprintf($conn, $pattern, $args);
 
     self::$futures[] = $this;
     $this->id = last_key(self::$futures);
   }
 
   public function isReady() {
     if ($this->result !== null || $this->exception) {
       return true;
     }
 
     if (!$this->conn->supportsAsyncQueries()) {
       if ($this->conn->supportsParallelQueries()) {
         $queries = array();
         $conns = array();
         foreach (self::$futures as $id => $future) {
           $queries[$id] = $future->query;
           $conns[$id] = $future->conn;
         }
         $results = $this->conn->executeParallelQueries($queries, $conns);
         $this->processResults($results);
         return true;
       }
 
       $conns = array();
       $conn_queries = array();
       foreach (self::$futures as $id => $future) {
         $hash = spl_object_hash($future->conn);
         $conns[$hash] = $future->conn;
         $conn_queries[$hash][$id] = $future->query;
       }
       foreach ($conn_queries as $hash => $queries) {
         $this->processResults($conns[$hash]->executeRawQueries($queries));
       }
       return true;
     }
 
     if (!$this->async) {
       $profiler = PhutilServiceProfiler::getInstance();
       $this->profilerCallID = $profiler->beginServiceCall(
         array(
           'type'    => 'query',
           'query'   => $this->query,
           'async'   => true,
         ));
 
       $this->async = $this->conn->asyncQuery($this->query);
       return false;
     }
 
     $conns = array();
     $asyncs = array();
     foreach (self::$futures as $id => $future) {
       if ($future->async) {
         $conns[$id] = $future->conn;
         $asyncs[$id] = $future->async;
       }
     }
 
     $this->processResults($this->conn->resolveAsyncQueries($conns, $asyncs));
 
     if ($this->result !== null || $this->exception) {
       return true;
     }
     return false;
   }
 
   private function processResults(array $results) {
     foreach ($results as $id => $result) {
       $future = self::$futures[$id];
       if ($result instanceof Exception) {
         $future->exception = $result;
       } else {
         $future->result = $result;
       }
       unset(self::$futures[$id]);
       if ($future->profilerCallID) {
         $profiler = PhutilServiceProfiler::getInstance();
         $profiler->endServiceCall($future->profilerCallID, array());
       }
     }
   }
 }
diff --git a/src/moduleutils/PhutilLibraryMapBuilder.php b/src/moduleutils/PhutilLibraryMapBuilder.php
index b2b25c0..5c2681a 100644
--- a/src/moduleutils/PhutilLibraryMapBuilder.php
+++ b/src/moduleutils/PhutilLibraryMapBuilder.php
@@ -1,486 +1,488 @@
 <?php
 
 /**
  * Build maps of libphutil libraries. libphutil uses the library map to locate
  * and load classes and functions in the library.
  *
  * @task map      Mapping libphutil Libraries
  * @task path     Path Management
  * @task symbol   Symbol Analysis and Caching
  * @task source   Source Management
  */
 final class PhutilLibraryMapBuilder {
 
   private $root;
   private $quiet = true;
   private $subprocessLimit = 8;
 
   private $fileSymbolMap;
   private $librarySymbolMap;
 
   const LIBRARY_MAP_VERSION_KEY   = '__library_version__';
   const LIBRARY_MAP_VERSION       = 2;
 
   const SYMBOL_CACHE_VERSION_KEY  = '__symbol_cache_version__';
   const SYMBOL_CACHE_VERSION      = 11;
 
 
 /* -(  Mapping libphutil Libraries  )---------------------------------------- */
 
   /**
    * Create a new map builder for a library.
    *
    * @param string Path to the library root.
    *
    * @task map
    */
   public function __construct($root) {
     $this->root = $root;
   }
 
   /**
    * Control status output. Use `--quiet` to set this.
    *
    * @param  bool  If true, don't show status output.
    * @return this
    *
    * @task map
    */
   public function setQuiet($quiet) {
     $this->quiet = $quiet;
     return $this;
   }
 
   /**
    * Control subprocess parallelism limit. Use `--limit` to set this.
    *
    * @param  int   Maximum number of subprocesses to run in parallel.
    * @return this
    *
    * @task map
    */
   public function setSubprocessLimit($limit) {
     $this->subprocessLimit = $limit;
     return $this;
   }
 
   /**
    * Get the map of symbols in this library, analyzing the library to build it
    * if necessary.
    *
    * @return map<string, wild> Information about symbols in this library.
    *
    * @task map
    */
   public function buildMap() {
     if ($this->librarySymbolMap === null) {
       $this->analyzeLibrary();
     }
     return $this->librarySymbolMap;
   }
 
 
   /**
    * Get the map of files in this library, analyzing the library to build it
    * if necessary.
    *
    * Returns a map of file paths to information about symbols used and defined
    * in the file.
    *
    * @return map<string, wild> Information about files in this library.
    *
    * @task map
    */
   public function buildFileSymbolMap() {
     if ($this->fileSymbolMap === null) {
       $this->analyzeLibrary();
     }
     return $this->fileSymbolMap;
   }
 
   /**
    * Build and update the library map.
    *
    * @return void
    *
    * @task map
    */
   public function buildAndWriteMap() {
     $library_map = $this->buildMap();
 
     $this->log("Writing map...\n");
     $this->writeLibraryMap($library_map);
   }
 
   /**
    * Write a status message to the user, if not running in quiet mode.
    *
    * @param  string  Message to write.
    * @return this
    *
    * @task map
    */
   private function log($message) {
     if (!$this->quiet) {
       @fwrite(STDERR, $message);
     }
     return $this;
   }
 
 
 /* -(  Path Management  )---------------------------------------------------- */
 
   /**
    * Get the path to some file in the library.
    *
    * @param  string  A library-relative path. If omitted, returns the library
    *                 root path.
    * @return string  An absolute path.
    *
    * @task path
    */
   private function getPath($path = '') {
     return $this->root.'/'.$path;
   }
 
   /**
    * Get the path to the symbol cache file.
    *
    * @return string Absolute path to symbol cache.
    *
    * @task path
    */
   private function getPathForSymbolCache() {
     return $this->getPath('.phutil_module_cache');
   }
 
   /**
    * Get the path to the map file.
    *
    * @return string Absolute path to the library map.
    *
    * @task path
    */
   private function getPathForLibraryMap() {
     return $this->getPath('__phutil_library_map__.php');
   }
 
   /**
    * Get the path to the library init file.
    *
    * @return string Absolute path to the library init file
    *
    * @task path
    */
   private function getPathForLibraryInit() {
     return $this->getPath('__phutil_library_init__.php');
   }
 
 
 /* -(  Symbol Analysis and Caching  )---------------------------------------- */
 
   /**
    * Load the library symbol cache, if it exists and is readable and valid.
    *
    * @return dict  Map of content hashes to cache of output from
    *               `phutil_symbols.php`.
    *
    * @task symbol
    */
   private function loadSymbolCache() {
     $cache_file = $this->getPathForSymbolCache();
 
     try {
       $cache = Filesystem::readFile($cache_file);
     } catch (Exception $ex) {
       $cache = null;
     }
 
     $symbol_cache = array();
     if ($cache) {
       $symbol_cache = json_decode($cache, true);
       if (!is_array($symbol_cache)) {
         $symbol_cache = array();
       }
     }
 
     $version = idx($symbol_cache, self::SYMBOL_CACHE_VERSION_KEY);
     if ($version != self::SYMBOL_CACHE_VERSION) {
       // Throw away caches from a different version of the library.
       $symbol_cache = array();
     }
     unset($symbol_cache[self::SYMBOL_CACHE_VERSION_KEY]);
 
     return $symbol_cache;
   }
 
   /**
    * Write a symbol map to disk cache.
    *
    * @param  dict  Symbol map of relative paths to symbols.
    * @param  dict  Source map (like @{method:loadSourceFileMap}).
    * @return void
    *
    * @task symbol
    */
   private function writeSymbolCache(array $symbol_map, array $source_map) {
     $cache_file = $this->getPathForSymbolCache();
 
     $cache = array(
       self::SYMBOL_CACHE_VERSION_KEY => self::SYMBOL_CACHE_VERSION,
     );
 
     foreach ($symbol_map as $file => $symbols) {
       $cache[$source_map[$file]] = $symbols;
     }
 
     $json = json_encode($cache);
     try {
       Filesystem::writeFile($cache_file, $json);
     } catch (FilesystemException $ex) {
       $this->log("Unable to save the cache!\n");
     }
   }
 
   /**
    * Drop the symbol cache, forcing a clean rebuild.
    *
    * @return this
    *
    * @task symbol
    */
   public function dropSymbolCache() {
     $this->log("Dropping symbol cache...\n");
     Filesystem::remove($this->getPathForSymbolCache());
   }
 
   /**
    * Build a future which returns a `phutil_symbols.php` analysis of a source
    * file.
    *
    * @param  string  Relative path to the source file to analyze.
    * @return Future  Analysis future.
    *
    * @task symbol
    */
   private function buildSymbolAnalysisFuture($file) {
     $absolute_file = $this->getPath($file);
     $bin = dirname(__FILE__).'/../../scripts/phutil_symbols.php';
 
     return new ExecFuture('php %s --ugly -- %s', $bin, $absolute_file);
   }
 
 
 /* -(  Source Management  )-------------------------------------------------- */
 
   /**
    * Build a map of all source files in a library to hashes of their content.
    * Returns an array like this:
    *
    *   array(
    *     'src/parser/ExampleParser.php' => '60b725f10c9c85c70d97880dfe8191b3',
    *     // ...
    *   );
    *
    * @return dict  Map of library-relative paths to content hashes.
    * @task source
    */
   private function loadSourceFileMap() {
     $root = $this->getPath();
 
     $init = $this->getPathForLibraryInit();
     if (!Filesystem::pathExists($init)) {
       throw new Exception("Provided path '{$root}' is not a phutil library.");
     }
 
     $files = id(new FileFinder($root))
       ->withType('f')
       ->withSuffix('php')
       ->excludePath('*/.*')
       ->setGenerateChecksums(true)
       ->find();
 
     $map = array();
     foreach ($files as $file => $hash) {
       $file = Filesystem::readablePath($file, $root);
       $file = ltrim($file, '/');
 
       if (dirname($file) == '.') {
         // We don't permit normal source files at the root level, so just ignore
         // them; they're special library files.
         continue;
       }
 
       if (dirname($file) == 'extensions') {
         // Ignore files in the extensions/ directory.
         continue;
       }
 
       // We include also filename in the hash to handle cases when the file is
       // moved without modifying its content.
       $map[$file] = md5($hash.$file);
     }
 
     return $map;
   }
 
   /**
    * Convert the symbol analysis of all the source files in the library into
    * a library map.
    *
    * @param   dict  Symbol analysis of all source files.
    * @return  dict  Library map.
    * @task source
    */
   private function buildLibraryMap(array $symbol_map) {
     $library_map = array(
       'class'     => array(),
       'function'  => array(),
       'xmap'      => array(),
     );
 
     // Detect duplicate symbols within the library.
     foreach ($symbol_map as $file => $info) {
       foreach ($info['have'] as $type => $symbols) {
         foreach ($symbols as $symbol => $declaration) {
           $lib_type = ($type == 'interface') ? 'class' : $type;
           if (!empty($library_map[$lib_type][$symbol])) {
             $prior = $library_map[$lib_type][$symbol];
             throw new Exception(
               "Definition of {$type} '{$symbol}' in file '{$file}' duplicates ".
               "prior definition in file '{$prior}'. You can not declare the ".
               "same symbol twice.");
           }
           $library_map[$lib_type][$symbol] = $file;
         }
       }
       $library_map['xmap'] += $info['xmap'];
     }
 
     // Simplify the common case (one parent) to make the file a little easier
     // to deal with.
     foreach ($library_map['xmap'] as $class => $extends) {
       if (count($extends) == 1) {
         $library_map['xmap'][$class] = reset($extends);
       }
     }
 
     // Sort the map so it is relatively stable across changes.
     foreach ($library_map as $key => $symbols) {
       ksort($symbols);
       $library_map[$key] = $symbols;
     }
     ksort($library_map);
 
     return $library_map;
   }
 
   /**
    * Write a finalized library map.
    *
    * @param  dict Library map structure to write.
    * @return void
    *
    * @task source
    */
   private function writeLibraryMap(array $library_map) {
     $map_file = $this->getPathForLibraryMap();
     $version = self::LIBRARY_MAP_VERSION;
 
     $library_map = array(
       self::LIBRARY_MAP_VERSION_KEY => $version,
     ) + $library_map;
 
     $library_map = phutil_var_export($library_map);
     $at = '@';
 
     $source_file = <<<EOPHP
 <?php
 
 /**
  * This file is automatically generated. Use 'arc liberate' to rebuild it.
  *
  * {$at}generated
  * {$at}phutil-library-version {$version}
  */
 phutil_register_library_map({$library_map});
 
 EOPHP;
 
     Filesystem::writeFile($map_file, $source_file);
   }
 
 
   /**
    * Analyze the library, generating the file and symbol maps.
    *
    * @return void
    */
   private function analyzeLibrary() {
     // Identify all the ".php" source files in the library.
     $this->log("Finding source files...\n");
     $source_map = $this->loadSourceFileMap();
     $this->log("Found ".number_format(count($source_map))." files.\n");
 
     // Load the symbol cache with existing parsed symbols. This allows us
     // to remap libraries quickly by analyzing only changed files.
     $this->log("Loading symbol cache...\n");
     $symbol_cache = $this->loadSymbolCache();
 
     // Build out the symbol analysis for all the files in the library. For
     // each file, check if it's in cache. If we miss in the cache, do a fresh
     // analysis.
     $symbol_map = array();
     $futures = array();
     foreach ($source_map as $file => $hash) {
       if (!empty($symbol_cache[$hash])) {
         $symbol_map[$file] = $symbol_cache[$hash];
         continue;
       }
       $futures[$file] = $this->buildSymbolAnalysisFuture($file);
     }
     $this->log("Found ".number_format(count($symbol_map))." files in cache.\n");
 
     // Run the analyzer on any files which need analysis.
     if ($futures) {
       $limit = $this->subprocessLimit;
       $count = number_format(count($futures));
 
       $this->log("Analyzing {$count} files with {$limit} subprocesses...\n");
 
       $progress = new PhutilConsoleProgressBar();
       if ($this->quiet) {
         $progress->setQuiet(true);
       }
       $progress->setTotal(count($futures));
 
-      foreach (Futures($futures)->limit($limit) as $file => $future) {
+      $futures = id(new FutureIterator($futures))
+        ->limit($limit);
+      foreach ($futures as $file => $future) {
         $result = $future->resolveJSON();
         if (empty($result['error'])) {
           $symbol_map[$file] = $result;
         } else {
           $progress->done(false);
           throw new XHPASTSyntaxErrorException(
             $result['line'],
             $file.': '.$result['error']);
         }
         $progress->update(1);
       }
       $progress->done();
     }
 
     $this->fileSymbolMap = $symbol_map;
 
     // We're done building the cache, so write it out immediately. Note that
     // we've only retained entries for files we found, so this implicitly cleans
     // out old cache entries.
     $this->writeSymbolCache($symbol_map, $source_map);
 
     // Our map is up to date, so either show it on stdout or write it to disk.
     $this->log("Building library map...\n");
 
     $this->librarySymbolMap = $this->buildLibraryMap($symbol_map);
   }
 
 
 }