diff --git a/.arcbuild b/.arcbuild new file mode 100644 --- /dev/null +++ b/.arcbuild @@ -0,0 +1,3 @@ +{ + "build_directory": "build-arcanist" +} diff --git a/arcanist/.phutil_module_cache b/arcanist/.phutil_module_cache --- a/arcanist/.phutil_module_cache +++ b/arcanist/.phutil_module_cache @@ -1 +1 @@ -{"__symbol_cache_version__":11,"d60c8224f471e0ecddc2a6f3c6839cd1":{"have":{"class":{"AutoPEP8FormatLinter":75}},"need":{"function":{"pht":297,"id":1317},"class":{"ArcanistExternalLinter":104,"ArcanistLintMessage":1324,"Filesystem":1168,"ArcanistLinter":1431,"ArcanistLintSeverity":1509}},"xmap":{"AutoPEP8FormatLinter":["ArcanistExternalLinter"]}},"213c3145da34ed6dfc0d70d628a2a086":{"have":{"class":{"CheckDocLinter":106}},"need":{"function":{"pht":323,"id":1868},"class":{"ArcanistExternalLinter":129,"ArcanistLintMessage":1875,"Filesystem":737,"ArcanistLinter":1923,"ArcanistLintSeverity":2009}},"xmap":{"CheckDocLinter":["ArcanistExternalLinter"]}},"7bab1f879b8a86dd9977b8c0d075935f":{"have":{"class":{"ClangFormatLinter":79}},"need":{"function":{"pht":302,"execx":787,"id":1664},"class":{"ArcanistExternalLinter":105,"ArcanistLintMessage":1671,"Filesystem":1515,"ArcanistLinter":1778,"ArcanistLintSeverity":1856}},"xmap":{"ClangFormatLinter":["ArcanistExternalLinter"]}},"0e068a1116ed03e86a2388020a821983":{"have":{"class":{"PythonFormatLinter":75}},"need":{"function":{"pht":305,"id":1614},"class":{"ArcanistExternalLinter":102,"ArcanistLintMessage":1621,"Filesystem":733,"ArcanistLinter":1752,"ArcanistLintSeverity":1835}},"xmap":{"PythonFormatLinter":["ArcanistExternalLinter"]}},"ea2beb1668dfbdd87488f18fbb20178f":{"have":{"class":{"TestsLinter":103}},"need":{"function":{"pht":318,"id":2676},"class":{"ArcanistExternalLinter":123,"ArcanistLintMessage":2683,"Filesystem":791,"ArcanistLinter":2731,"ArcanistLintSeverity":2839}},"xmap":{"TestsLinter":["ArcanistExternalLinter"]}}} \ No newline at end of file +{"__symbol_cache_version__":11,"90a8b110dc475955f15bb81d37268cb5":{"have":{"class":{"AutoPEP8FormatLinter":75}},"need":{"function":{"pht":297,"execx":769,"id":1903},"class":{"ArcanistExternalLinter":104,"ArcanistLintMessage":1910,"Filesystem":1754,"ArcanistLinter":2017,"ArcanistLintSeverity":2095}},"xmap":{"AutoPEP8FormatLinter":["ArcanistExternalLinter"]}},"bf0805c02029a7226e8c0d7dee039b3c":{"have":{"class":{"CheckDocLinter":106}},"need":{"function":{"pht":323,"id":1847},"class":{"ArcanistExternalLinter":129,"ArcanistLintMessage":1854,"Filesystem":731,"ArcanistLinter":1902,"ArcanistLintSeverity":1988}},"xmap":{"CheckDocLinter":["ArcanistExternalLinter"]}},"6af7410cfea496ff1d4dcc2624b6b8ea":{"have":{"class":{"ClangFormatLinter":79}},"need":{"function":{"pht":302,"execx":781,"id":1653},"class":{"ArcanistExternalLinter":105,"ArcanistLintMessage":1660,"Filesystem":1504,"ArcanistLinter":1767,"ArcanistLintSeverity":1845}},"xmap":{"ClangFormatLinter":["ArcanistExternalLinter"]}},"6f2f22dd0f259fb2eaa284b4fab3bc29":{"have":{"class":{"PythonFormatLinter":123}},"need":{"function":{"pht":353,"id":1838},"class":{"ArcanistExternalLinter":150,"ArcanistLintMessage":1845,"Filesystem":776,"ArcanistLinter":1970,"ArcanistLintSeverity":2053}},"xmap":{"PythonFormatLinter":["ArcanistExternalLinter"]}},"25781df78f6eebfb223296b8265e9d19":{"have":{"class":{"TestsLinter":103}},"need":{"function":{"pht":318,"id":2629},"class":{"ArcanistExternalLinter":123,"ArcanistLintMessage":2636,"Filesystem":776,"ArcanistLinter":2684,"ArcanistLintSeverity":2792}},"xmap":{"TestsLinter":["ArcanistExternalLinter"]}},"e6113630a098d3a50f65500a5c577a98":{"have":{"class":{"ArcanistBuildWorkflow":48}},"need":{"function":{"phutil_console_format":342,"pht":958,"phutil_json_decode":3778},"class":{"ArcanistWorkflow":78,"ExecFuture":1433,"PhutilConsole":1016,"Filesystem":2165},"class\/interface":{"FilesystemException":2435,"PhutilJSONParserException":3824}},"xmap":{"ArcanistBuildWorkflow":["ArcanistWorkflow"]}}} \ No newline at end of file diff --git a/arcanist/__phutil_library_map__.php b/arcanist/__phutil_library_map__.php --- a/arcanist/__phutil_library_map__.php +++ b/arcanist/__phutil_library_map__.php @@ -9,6 +9,7 @@ phutil_register_library_map(array( '__library_version__' => 2, 'class' => array( + 'ArcanistBuildWorkflow' => 'workflow/ArcanistBuildWorkflow.php', 'AutoPEP8FormatLinter' => 'linter/AutoPEP8Linter.php', 'CheckDocLinter' => 'linter/CheckDocLinter.php', 'ClangFormatLinter' => 'linter/ClangFormatLinter.php', @@ -17,6 +18,7 @@ ), 'function' => array(), 'xmap' => array( + 'ArcanistBuildWorkflow' => 'ArcanistWorkflow', 'AutoPEP8FormatLinter' => 'ArcanistExternalLinter', 'CheckDocLinter' => 'ArcanistExternalLinter', 'ClangFormatLinter' => 'ArcanistExternalLinter', diff --git a/arcanist/workflow/ArcanistBuildWorkflow.php b/arcanist/workflow/ArcanistBuildWorkflow.php new file mode 100644 --- /dev/null +++ b/arcanist/workflow/ArcanistBuildWorkflow.php @@ -0,0 +1,205 @@ + 'targets', + ); + } + + public function requiresWorkingCopy() { + return true; + } + + private function setSuccess($duration) { + $out = phutil_console_format("** %s ** %s (%.3f s)\n", + pht('OKAY'), pht('Build is successful'), $duration); + + PhutilConsole::getConsole()->writeOut($out); + + return self::RESULT_OKAY; + } + + private function setFailure($failureReason) { + $out = phutil_console_format( "** %s ** %s\n", pht('FAILED'), + pht($failureReason)); + + PhutilConsole::getConsole()->writeOut($out); + + return self::RESULT_ERRORS; + } + + private function build() { + /* Run CMake from the build repository */ + $future = new ExecFuture( + 'cmake -GNinja '.$this->getWorkingCopy()->getProjectRoot()); + $future->setCWD($this->buildDir); + list($ret) = $future->resolve(); + + if ($ret != 0) { + return false; + } + + /* + * Run Ninja from the build directory. + * If "targets" arguments are specified, pass them to Ninja, otherwise do + * not speficy any target and let Ninja build the default. + */ + $targets = $this->getArgument('targets'); + $cmd = 'ninja'; + foreach ($targets as $_) { + $cmd .= ' %s'; + } + + $future = new ExecFuture($cmd, ...$targets); + $future->setCWD($this->buildDir); + list($ret) = $future->resolve(); + + return $ret == 0; + } + + private function removeBuildDirectory() { + if (!Filesystem::pathExists($this->buildDir)) { + return true; + } + + if (Filesystem::pathsAreEquivalent($this->buildDir, + $this->getWorkingCopy()->getProjectRoot())) { + return false; + } + + try { + Filesystem::remove($this->buildDir); + } catch (FilesystemException $e) { + return false; + } + + return true; + } + + public function run() { + if (!Filesystem::binaryExists('cmake')) { + return $this->setFailure( + 'The `cmake` command is required to run `arc build`. Please install '. + 'CMake (https://cmake.org) then run `arc build` again.'); + } + + if (!Filesystem::binaryExists('ninja')) { + return $this->setFailure( + 'The `ninja` command is required to run `arc build`. Please install '. + 'Ninja (https://ninja-build.org) hen run `arc build` again.'); + } + + $workingCopy = $this->getWorkingCopy(); + + /* + * Create the build directory if it doesn't exist. + * The build directory is read from the option + * `build_directory` in the `.arcbuild` configuration file. + * + * If the directory already exists, remove it first. + */ + + /* Find the .arcbuild configuration file. */ + $configPath = $workingCopy->getProjectPath('.arcbuild'); + if (!Filesystem::pathExists($configPath)) { + return $this->setFailure( + 'Unable to find the `.arcbuild` configuration file. Please create the '. + 'file at the root of the project.'); + } + + /* Read the .arcbuild configuration file, check it is a valid json file. */ + $configData = Filesystem::readFile($configPath); + try { + $config = phutil_json_decode($configData); + } catch (PhutilJSONParserException $e) { + return $this->setFailure( + 'The `.arcbuild` file is not a valid JSON file, please check for '. + 'syntax errors.'); + } + + /* Make sure the `build_directory` option is set. */ + if (!array_key_exists('build_directory', $config)) { + return $this->setFailure( + 'No build directory is configured. Set the `build_directory` option '. + 'in your `.arcbuild` configuration file and run `arc build` again.'); + } + + $root = $workingCopy->getProjectRoot(); + $this->buildDir = Filesystem::resolvePath($config['build_directory'], + $root); + + /* Disallow to set the project root as the build directory. */ + if (Filesystem::pathsAreEquivalent($this->buildDir, $root)) { + return $this->setFailure( + 'The build directory is set to the project root: '.$this->buildDir.'. '. + 'The directory is removed after the build, please set it to something '. + 'else.'); + } + + /* If the build directory exists, try to remove it first. */ + if (!$this->removeBuildDirectory()) { + return $this->setFailure( + 'The build directory '.$this->buildDir.' already exists and cannot be '. + 'deleted. Please check the permissions or delete it manually then '. + 'run `arc build` again.'); + } + + /* Create the build directory. */ + try { + $this->buildDir = Filesystem::createDirectory($this->buildDir, 0755, + true); + } catch (FilesystemException $e) { + return $this->setFailure( + 'Unable to create the build directory: '.$this->buildDir.'. Check you '. + 'have write permissions to the parent directory and run `arc build` '. + 'again.'); + } + + /* Build the project, and measure the build duration. */ + $timeStart = microtime(true); + + if (!$this->build()) { + return $this->setFailure('Build failed !'); + } + + $timeEnd = microtime(true); + + return $this->setSuccess($timeEnd - $timeStart); + } + + public function finalize() { + $this->removeBuildDirectory(); + } +}