public static function newAPIFromWorkingCopyIdentity(ArcanistWorkingCopyIdentity $working_copy)
 {
     $root = $working_copy->getProjectRoot();
     if (!$root) {
         throw new ArcanistUsageException("There is no readable '.arcconfig' file in the working directory or " . "any parent directory. Create an '.arcconfig' file to configure arc.");
     }
     if (Filesystem::pathExists($root . '/.hg')) {
         $api = new ArcanistMercurialAPI($root);
         $api->workingCopyIdentity = $working_copy;
         return $api;
     }
     $git_root = self::discoverGitBaseDirectory($root);
     if ($git_root) {
         if (!Filesystem::pathsAreEquivalent($root, $git_root)) {
             throw new ArcanistUsageException("'.arcconfig' file is located at '{$root}', but working copy root " . "is '{$git_root}'. Move '.arcconfig' file to the working copy root.");
         }
         $api = new ArcanistGitAPI($root);
         $api->workingCopyIdentity = $working_copy;
         return $api;
     }
     // check if we're in an svn working copy
     foreach (Filesystem::walkToRoot($root) as $dir) {
         if (Filesystem::pathExists($dir . '/.svn')) {
             $api = new ArcanistSubversionAPI($root);
             $api->workingCopyIdentity = $working_copy;
             return $api;
         }
     }
     throw new ArcanistUsageException("The current working directory is not part of a working copy for a " . "supported version control system (svn, git or mercurial).");
 }
 public function testWalkToRoot()
 {
     $test_cases = array(array(dirname(__FILE__) . '/data/include_dir.txt/subdir.txt/test', dirname(__FILE__), array(dirname(__FILE__) . '/data/include_dir.txt/subdir.txt/test', dirname(__FILE__) . '/data/include_dir.txt/subdir.txt', dirname(__FILE__) . '/data/include_dir.txt', dirname(__FILE__) . '/data', dirname(__FILE__))), array(dirname(__FILE__) . '/data/include_dir.txt/subdir.txt', dirname(__FILE__), array(dirname(__FILE__) . '/data/include_dir.txt/subdir.txt', dirname(__FILE__) . '/data/include_dir.txt', dirname(__FILE__) . '/data', dirname(__FILE__))), 'root and path are identical' => array(dirname(__FILE__), dirname(__FILE__), array(dirname(__FILE__))), 'root is not an ancestor of path' => array(dirname(__FILE__), dirname(__FILE__) . '/data/include_dir.txt/subdir.txt', array()), 'fictional paths work' => array('/x/y/z', '/', array('/x/y/z', '/x/y', '/x', '/')));
     foreach ($test_cases as $test_case) {
         list($path, $root, $expected) = $test_case;
         $this->assertEqual($expected, Filesystem::walkToRoot($path, $root));
     }
 }
예제 #3
0
function phutil_get_library_root_for_path($path)
{
    foreach (Filesystem::walkToRoot($path) as $dir) {
        if (@file_exists($dir . '/__phutil_library_init__.php')) {
            return $dir;
        }
    }
    return null;
}
 public function run()
 {
     $argv = $this->getArgument('argv');
     if (count($argv) > 1) {
         throw new ArcanistUsageException("Provide only one path to 'arc liberate'. The path should be a " . "directory where you want to create or update a libphutil library.");
     } else {
         if (count($argv) == 0) {
             $path = getcwd();
         } else {
             $path = reset($argv);
         }
     }
     $is_remap = $this->getArgument('remap');
     $is_verify = $this->getArgument('verify');
     $path = Filesystem::resolvePath($path);
     if (Filesystem::pathExists($path) && is_dir($path)) {
         $init = id(new FileFinder($path))->withPath('*/__phutil_library_init__.php')->find();
     } else {
         $init = null;
     }
     if ($init) {
         if (count($init) > 1) {
             throw new ArcanistUsageException("Specified directory contains more than one libphutil library. Use " . "a more specific path.");
         }
         $path = Filesystem::resolvePath(dirname(reset($init)), $path);
     } else {
         $found = false;
         foreach (Filesystem::walkToRoot($path) as $dir) {
             if (Filesystem::pathExists($dir . '/__phutil_library_init__.php')) {
                 $path = $dir;
                 $found = true;
                 break;
             }
         }
         if (!$found) {
             echo "No library currently exists at that path...\n";
             $this->liberateCreateDirectory($path);
             $this->liberateCreateLibrary($path);
             return;
         }
     }
     $version = $this->getLibraryFormatVersion($path);
     switch ($version) {
         case 1:
             if ($this->getArgument('upgrade')) {
                 return $this->upgradeLibrary($path);
             }
             throw new ArcanistUsageException("This library is using libphutil v1, which is no longer supported. " . "Run 'arc liberate --upgrade' to upgrade to v2.");
         case 2:
             if ($this->getArgument('upgrade')) {
                 throw new ArcanistUsageException("Can't upgrade a v2 library!");
             }
             return $this->liberateVersion2($path);
         default:
             throw new ArcanistUsageException("Unknown library version '{$version}'!");
     }
 }
 public function getMetadataPath()
 {
     static $svn_dir = null;
     if ($svn_dir === null) {
         // from svn 1.7, subversion keeps a single .svn directly under
         // the working copy root.  However, we allow .arcconfigs that
         // aren't at the working copy root.
         foreach (Filesystem::walkToRoot($this->getPath()) as $parent) {
             $possible_svn_dir = Filesystem::resolvePath('.svn', $parent);
             if (Filesystem::pathExists($possible_svn_dir)) {
                 $svn_dir = $possible_svn_dir;
                 break;
             }
         }
     }
     return $svn_dir;
 }
 public static function newFromPath($path)
 {
     $project_id = null;
     $project_root = null;
     $config = array();
     foreach (Filesystem::walkToRoot($path) as $dir) {
         $config_file = $dir . '/.arcconfig';
         if (!Filesystem::pathExists($config_file)) {
             continue;
         }
         $proj_raw = Filesystem::readFile($config_file);
         $config = self::parseRawConfigFile($proj_raw, $config_file);
         $project_root = $dir;
         break;
     }
     return new ArcanistWorkingCopyIdentity($project_root, $config);
 }
 /**
  * Locate all the information we need about a directory which we presume
  * to be a working copy. Particularly, we want to discover:
  *
  *   - Is the directory inside a working copy (hg, git, svn)?
  *   - If so, what is the root of the working copy?
  *   - Is there a `.arcconfig` file?
  *
  * This is complicated, mostly because Subversion has special rules. In
  * particular:
  *
  *   - Until 1.7, Subversion put a `.svn/` directory inside //every//
  *     directory in a working copy. After 1.7, it //only// puts one at the
  *     root.
  *   - We allow `.arcconfig` to appear anywhere in a Subversion working copy,
  *     and use the one closest to the directory.
  *   - Although we may use a `.arcconfig` from a subdirectory, we store
  *     metadata in the root's `.svn/`, because it's the only one guaranteed
  *     to exist.
  *
  * Users also do these kinds of things in the wild:
  *
  *   - Put working copies inside other working copies.
  *   - Put working copies inside `.git/` directories.
  *   - Create `.arcconfig` files at `/.arcconfig`, `/home/.arcconfig`, etc.
  *
  * This method attempts to be robust against all sorts of possible
  * misconfiguration.
  *
  * @param string    Path to load information for, usually the current working
  *                  directory (unless running unit tests).
  * @param map|null  Pass `null` to locate and load a `.arcconfig` file if one
  *                  exists. Pass a map to use it to set configuration.
  * @return ArcanistWorkingCopyIdentity Constructed working copy identity.
  */
 private static function newFromPathWithConfig($path, $config)
 {
     $project_root = null;
     $vcs_root = null;
     $vcs_type = null;
     // First, find the outermost directory which is a Git, Mercurial or
     // Subversion repository, if one exists. We go from the top because this
     // makes it easier to identify the root of old SVN working copies (which
     // have a ".svn/" directory inside every directory in the working copy) and
     // gives us the right result if you have a Git repository inside a
     // Subversion repository or something equally ridiculous.
     $paths = Filesystem::walkToRoot($path);
     $config_paths = array();
     $paths = array_reverse($paths);
     foreach ($paths as $path_key => $parent_path) {
         $try = array('git' => $parent_path . '/.git', 'hg' => $parent_path . '/.hg', 'svn' => $parent_path . '/.svn');
         foreach ($try as $vcs => $try_dir) {
             if (!Filesystem::pathExists($try_dir)) {
                 continue;
             }
             // NOTE: We're distinguishing between the `$project_root` and the
             // `$vcs_root` because they may not be the same in Subversion. Normally,
             // they are identical. However, in Subversion, the `$vcs_root` is the
             // base directory of the working copy (the directory which has the
             // `.svn/` directory, after SVN 1.7), while the `$project_root` might
             // be any subdirectory of the `$vcs_root`: it's the the directory
             // closest to the current directory which contains a `.arcconfig`.
             $project_root = $parent_path;
             $vcs_root = $parent_path;
             $vcs_type = $vcs;
             if ($vcs == 'svn') {
                 // For Subversion, we'll look for a ".arcconfig" file here or in
                 // any subdirectory, starting at the deepest subdirectory.
                 $config_paths = array_slice($paths, $path_key);
                 $config_paths = array_reverse($config_paths);
             } else {
                 // For Git and Mercurial, we'll only look for ".arcconfig" right here.
                 $config_paths = array($parent_path);
             }
             break;
         }
     }
     $console = PhutilConsole::getConsole();
     $looked_in = array();
     foreach ($config_paths as $config_path) {
         $config_file = $config_path . '/.arcconfig';
         $looked_in[] = $config_file;
         if (Filesystem::pathExists($config_file)) {
             // We always need to examine the filesystem to look for `.arcconfig`
             // so we can set the project root correctly. We might or might not
             // actually read the file: if the caller passed in configuration data,
             // we'll ignore the actual file contents.
             $project_root = $config_path;
             if ($config === null) {
                 $console->writeLog("%s\n", pht('Working Copy: Reading .arcconfig from "%s".', $config_file));
                 $config_data = Filesystem::readFile($config_file);
                 $config = self::parseRawConfigFile($config_data, $config_file);
             }
             break;
         }
     }
     if ($config === null) {
         if ($looked_in) {
             $console->writeLog("%s\n", pht('Working Copy: Unable to find .arcconfig in any of these ' . 'locations: %s.', implode(', ', $looked_in)));
         } else {
             $console->writeLog("%s\n", pht('Working Copy: No candidate locations for .arcconfig from ' . 'this working directory.'));
         }
         $config = array();
     }
     if ($project_root === null) {
         // We aren't in a working directory at all. This is fine if we're
         // running a command like "arc help". If we're running something that
         // requires a working directory, an exception will be raised a little
         // later on.
         $console->writeLog("%s\n", pht('Working Copy: Path "%s" is not in any working copy.', $path));
         return new ArcanistWorkingCopyIdentity($path, $config);
     }
     $console->writeLog("%s\n", pht('Working Copy: Path "%s" is part of `%s` working copy "%s".', $path, $vcs_type, $vcs_root));
     $console->writeLog("%s\n", pht('Working Copy: Project root is at "%s".', $project_root));
     $identity = new ArcanistWorkingCopyIdentity($project_root, $config);
     $identity->localMetaDir = $vcs_root . '/.' . $vcs_type;
     $identity->localConfig = $identity->readLocalArcConfig();
     $identity->vcsType = $vcs_type;
     $identity->vcsRoot = $vcs_root;
     return $identity;
 }
 /**
  * Returns the paths in which we should look for tests to execute.
  *
  * @return list<string>  A list of paths in which to search for test cases.
  */
 public function getTestPaths()
 {
     $root = $this->getWorkingCopy()->getProjectRoot();
     $paths = array();
     foreach ($this->getPaths() as $path) {
         $library_root = phutil_get_library_root_for_path($path);
         if (!$library_root) {
             continue;
         }
         $library_name = phutil_get_library_name_for_root($library_root);
         if (!$library_name) {
             throw new Exception(pht("Attempting to run unit tests on a libphutil library which has " . "not been loaded, at:\n\n" . "    %s\n\n" . "This probably means one of two things:\n\n" . "    - You may need to add this library to %s.\n" . "    - You may be running tests on a copy of libphutil or " . "arcanist using a different copy of libphutil or arcanist. " . "This operation is not supported.\n", $library_root, '.arcconfig.'));
         }
         $path = Filesystem::resolvePath($path, $root);
         $library_path = Filesystem::readablePath($path, $library_root);
         if (!Filesystem::isDescendant($path, $library_root)) {
             // We have encountered some kind of symlink maze -- for instance, $path
             // is some symlink living outside the library that links into some file
             // inside the library. Just ignore these cases, since the affected file
             // does not actually lie within the library.
             continue;
         }
         if (is_file($path) && preg_match('@(?:^|/)__tests__/@', $path)) {
             $paths[$library_name . ':' . $library_path] = array('library' => $library_name, 'path' => $library_path);
             continue;
         }
         foreach (Filesystem::walkToRoot($path, $library_root) as $subpath) {
             if ($subpath == $library_root) {
                 $paths[$library_name . ':.'] = array('library' => $library_name, 'path' => '__tests__/');
             } else {
                 $library_subpath = Filesystem::readablePath($subpath, $library_root);
                 $paths[$library_name . ':' . $library_subpath] = array('library' => $library_name, 'path' => $library_subpath . '/__tests__/');
             }
         }
     }
     return $paths;
 }
예제 #9
0
 /**
  * Get places to look for PHP Unit tests that cover a given file. For some
  * file "/a/b/c/X.php", we look in the same directory:
  *
  *  /a/b/c/
  *
  * We then look in all parent directories for a directory named "tests/"
  * (or "Tests/"):
  *
  *  /a/b/c/tests/
  *  /a/b/tests/
  *  /a/tests/
  *  /tests/
  *
  * We also try to replace each directory component with "tests/":
  *
  *  /a/b/tests/
  *  /a/tests/c/
  *  /tests/b/c/
  *
  * We also try to add "tests/" at each directory level:
  *
  *  /a/b/c/tests/
  *  /a/b/tests/c/
  *  /a/tests/b/c/
  *  /tests/a/b/c/
  *
  * This finds tests with a layout like:
  *
  *  docs/
  *  src/
  *  tests/
  *
  * ...or similar. This list will be further pruned by the caller; it is
  * intentionally filesystem-agnostic to be unit testable.
  *
  * @param   string        PHP file to locate test cases for.
  * @return  list<string>  List of directories to search for tests in.
  */
 public static function getSearchLocationsForTests($path)
 {
     $file = basename($path);
     $dir = dirname($path);
     $test_dir_names = array('tests', 'Tests');
     $try_directories = array();
     // Try in the current directory.
     $try_directories[] = array($dir);
     // Try in a tests/ directory anywhere in the ancestry.
     foreach (Filesystem::walkToRoot($dir) as $parent_dir) {
         if ($parent_dir == '/') {
             // We'll restore this later.
             $parent_dir = '';
         }
         foreach ($test_dir_names as $test_dir_name) {
             $try_directories[] = array($parent_dir, $test_dir_name);
         }
     }
     // Try replacing each directory component with 'tests/'.
     $parts = trim($dir, DIRECTORY_SEPARATOR);
     $parts = explode(DIRECTORY_SEPARATOR, $parts);
     foreach (array_reverse(array_keys($parts)) as $key) {
         foreach ($test_dir_names as $test_dir_name) {
             $try = $parts;
             $try[$key] = $test_dir_name;
             array_unshift($try, '');
             $try_directories[] = $try;
         }
     }
     // Try adding 'tests/' at each level.
     foreach (array_reverse(array_keys($parts)) as $key) {
         foreach ($test_dir_names as $test_dir_name) {
             $try = $parts;
             $try[$key] = $test_dir_name . DIRECTORY_SEPARATOR . $try[$key];
             array_unshift($try, '');
             $try_directories[] = $try;
         }
     }
     $results = array();
     foreach ($try_directories as $parts) {
         $results[implode(DIRECTORY_SEPARATOR, $parts) . DIRECTORY_SEPARATOR] = true;
     }
     return array_keys($results);
 }
예제 #10
0
 /**
  * Some nasty guessing here.
  *
  * Walk up to the project root trying to find
  * [Tt]ests directory and replicate the structure there.
  *
  * Assume that the class path is
  * /www/project/module/package/subpackage/FooBar.php
  * and a project root is /www/project it will look for it by these paths:
  * /www/project/module/package/subpackage/[Tt]ests/FooBarTest.php
  * /www/project/module/package/[Tt]ests/subpackage/FooBarTest.php
  * /www/project/module/[Tt]ests/package/subpackage/FooBarTest.php
  * /www/project/Tt]ests/module/package/subpackage/FooBarTest.php
  *
  * TODO: Add support for finding tests based on PSR-1 naming conventions:
  * /www/project/src/Something/Foo/Bar.php tests should be detected in
  * /www/project/tests/Something/Foo/BarTest.php
  *
  * TODO: Add support for finding tests in testsuite folders from
  * phpunit.xml configuration.
  *
  * @param string $path
  *
  * @return string|boolean
  */
 private function findTestFile($path)
 {
     $expected_file = substr(basename($path), 0, -4) . 'Test.php';
     $expected_dir = null;
     $dirname = dirname($path);
     foreach (Filesystem::walkToRoot($dirname) as $dir) {
         $expected_dir = DIRECTORY_SEPARATOR . substr($dirname, strlen($dir) + 1) . $expected_dir;
         $look_for = $dir . DIRECTORY_SEPARATOR . '%s' . $expected_dir . $expected_file;
         if (Filesystem::pathExists(sprintf($look_for, 'Tests'))) {
             return sprintf($look_for, 'Tests');
         } else {
             if (Filesystem::pathExists(sprintf($look_for, 'Tests'))) {
                 return sprintf($look_for, 'Tests');
             }
         }
         if ($dir == $this->projectRoot) {
             break;
         }
     }
     return false;
 }
 protected function executeChecks()
 {
     // NOTE: We've already appended `environment.append-paths`, so we don't
     // need to explicitly check for it.
     $path = getenv('PATH');
     if (!$path) {
         $summary = pht('The environmental variable %s is empty. Phabricator will not ' . 'be able to execute some commands.', '$PATH');
         $message = pht("The environmental variable %s is empty. Phabricator needs to execute " . "some system commands, like `%s`, `%s`, `%s`, and `%s`. To execute " . "these commands, the binaries must be available in the webserver's " . "%s. You can set additional paths in Phabricator configuration.", '$PATH', 'svn', 'git', 'hg', 'diff', '$PATH');
         $this->newIssue('config.environment.append-paths')->setName(pht('%s Not Set', '$PATH'))->setSummary($summary)->setMessage($message)->addPhabricatorConfig('environment.append-paths');
         // Bail on checks below.
         return;
     }
     // Users are remarkably industrious at misconfiguring software. Try to
     // catch mistaken configuration of PATH.
     $path_parts = explode(PATH_SEPARATOR, $path);
     $bad_paths = array();
     foreach ($path_parts as $path_part) {
         if (!strlen($path_part)) {
             continue;
         }
         $message = null;
         $not_exists = false;
         foreach (Filesystem::walkToRoot($path_part) as $part) {
             if (!Filesystem::pathExists($part)) {
                 $not_exists = $part;
                 // Walk up so we can tell if this is a readability issue or not.
                 continue;
             } else {
                 if (!is_dir(Filesystem::resolvePath($part))) {
                     $message = pht("The PATH component '%s' (which resolves as the absolute path " . "'%s') is not usable because '%s' is not a directory.", $path_part, Filesystem::resolvePath($path_part), $part);
                 } else {
                     if (!is_readable($part)) {
                         $message = pht("The PATH component '%s' (which resolves as the absolute path " . "'%s') is not usable because '%s' is not readable.", $path_part, Filesystem::resolvePath($path_part), $part);
                     } else {
                         if ($not_exists) {
                             $message = pht("The PATH component '%s' (which resolves as the absolute path " . "'%s') is not usable because '%s' does not exist.", $path_part, Filesystem::resolvePath($path_part), $not_exists);
                         } else {
                             // Everything seems good.
                             break;
                         }
                     }
                 }
             }
             if ($message !== null) {
                 break;
             }
         }
         if ($message === null) {
             if (!phutil_is_windows() && !@file_exists($path_part . '/.')) {
                 $message = pht("The PATH component '%s' (which resolves as the absolute path " . "'%s') is not usable because it is not traversable (its '%s' " . "permission bit is not set).", $path_part, Filesystem::resolvePath($path_part), '+x');
             }
         }
         if ($message !== null) {
             $bad_paths[$path_part] = $message;
         }
     }
     if ($bad_paths) {
         foreach ($bad_paths as $path_part => $message) {
             $digest = substr(PhabricatorHash::digest($path_part), 0, 8);
             $this->newIssue('config.PATH.' . $digest)->setName(pht('%s Component Unusable', '$PATH'))->setSummary(pht('A component of the configured PATH can not be used by ' . 'the webserver: %s', $path_part))->setMessage(pht("The configured PATH includes a component which is not usable. " . "Phabricator will be unable to find or execute binaries located " . "here:" . "\n\n" . "%s" . "\n\n" . "The user that the webserver runs as must be able to read all " . "the directories in PATH in order to make use of them.", $message))->addPhabricatorConfig('environment.append-paths');
         }
     }
 }
 protected function __construct($root, array $config)
 {
     $this->projectRoot = $root;
     $this->projectConfig = $config;
     $this->localConfig = array();
     $vc_dirs = array('.git', '.hg', '.svn');
     $found_meta_dir = false;
     foreach ($vc_dirs as $dir) {
         $meta_path = Filesystem::resolvePath($dir, $this->projectRoot);
         if (Filesystem::pathExists($meta_path)) {
             $found_meta_dir = true;
             $local_path = Filesystem::resolvePath('arc/config', $meta_path);
             if (Filesystem::pathExists($local_path)) {
                 $file = Filesystem::readFile($local_path);
                 if ($file) {
                     $this->localConfig = json_decode($file, true);
                 }
             }
             break;
         }
     }
     if (!$found_meta_dir) {
         // Try for a single higher-level .svn directory as used by svn 1.7+
         foreach (Filesystem::walkToRoot($this->projectRoot) as $parent_path) {
             $local_path = Filesystem::resolvePath('.svn/arc/config', $parent_path);
             if (Filesystem::pathExists($local_path)) {
                 $file = Filesystem::readFile($local_path);
                 if ($file) {
                     $this->localConfig = json_decode($file, true);
                 }
             }
         }
     }
 }
예제 #13
0
 public function run()
 {
     $argv = $this->getArgument('argv');
     if (count($argv) > 1) {
         throw new ArcanistUsageException("Provide only one path to 'arc liberate'. The path should be a " . "directory where you want to create or update a libphutil library.");
     } else {
         if (count($argv) == 0) {
             $path = getcwd();
         } else {
             $path = reset($argv);
         }
     }
     $is_remap = $this->getArgument('remap');
     $is_verify = $this->getArgument('verify');
     $path = Filesystem::resolvePath($path);
     if (Filesystem::pathExists($path) && is_dir($path)) {
         $init = id(new FileFinder($path))->withPath('*/__phutil_library_init__.php')->find();
     } else {
         $init = null;
     }
     if ($init) {
         if (count($init) > 1) {
             throw new ArcanistUsageException("Specified directory contains more than one libphutil library. Use " . "a more specific path.");
         }
         $path = Filesystem::resolvePath(dirname(reset($init)), $path);
     } else {
         $found = false;
         foreach (Filesystem::walkToRoot($path) as $dir) {
             if (Filesystem::pathExists($dir . '/__phutil_library_init__.php')) {
                 $path = $dir;
                 break;
             }
         }
         if (!$found) {
             echo "No library currently exists at that path...\n";
             $this->liberateCreateDirectory($path);
             $this->liberateCreateLibrary($path);
         }
     }
     if ($this->getArgument('remap')) {
         return $this->liberateRunRemap($path);
     }
     if ($this->getArgument('verify')) {
         return $this->liberateRunVerify($path);
     }
     $readable = Filesystem::readablePath($path);
     echo "Using library root at '{$readable}'...\n";
     $this->checkForLooseFiles($path);
     if ($this->getArgument('all')) {
         echo "Dropping module cache...\n";
         Filesystem::remove($path . '/.phutil_module_cache');
     }
     echo "Mapping library...\n";
     // Force a rebuild of the library map before running lint. The remap
     // operation will load the map before regenerating it, so if a class has
     // been renamed (say, from OldClass to NewClass) this rebuild will
     // cause the initial remap to see NewClass and correctly remove includes
     // caused by use of OldClass.
     $this->liberateGetChangedPaths($path);
     $arc_bin = $this->getScriptPath('bin/arc');
     do {
         $future = new ExecFuture('%s liberate --remap -- %s', $arc_bin, $path);
         $wrote = $future->resolveJSON();
         foreach ($wrote as $wrote_path) {
             echo "Updated '{$wrote_path}'...\n";
         }
     } while ($wrote);
     echo "Verifying library...\n";
     $err = phutil_passthru('%s liberate --verify -- %s', $arc_bin, $path);
     $do_update = !$err || $this->getArgument('force-update');
     if ($do_update) {
         echo "Finalizing library map...\n";
         execx('%s %s', $this->getPhutilMapperLocation(), $path);
     }
     if ($err) {
         if ($do_update) {
             echo phutil_console_format("<bg:yellow>**  WARNING  **</bg> Library update forced, but lint " . "failures remain.\n");
         } else {
             echo phutil_console_format("<bg:red>**  UNRESOLVED LINT ERRORS  **</bg> This library has " . "unresolved lint failures. The library map was not updated. Use " . "--force-update to force an update.\n");
         }
     } else {
         echo phutil_console_format("<bg:green>**  OKAY  **</bg> Library updated.\n");
     }
     return $err;
 }