From c64f86c2f6321c8522f3246bb477288cb51cdd59 Mon Sep 17 00:00:00 2001 From: epriestley Date: Tue, 18 Sep 2018 13:46:12 -0700 Subject: [PATCH] [Wilds] Flesh out most of the new Config objects Summary: Ref T13098. This is pretty rough, but sketches out the general shape of a more modern configuration flow. The new flow is very similar to the Phabricator flow. Each configuration option is typed (string, bool, list of dictionaries, etc) so we can typecheck it, and each type is a class so the types are modular and can do fancy stuff. Some of this "fancy stuff" that I want to do includes: - transparently rewriting/reformatting various options for modernness/consistency; - having some options exposed as objects instead of raw JSON values (in particular, aliases); - merging "list" options (like "aliases") in a modular way instead of by having hard-coded stuff that says "this particular option is magic gets merged instead of getting replaced when defined in multiple places". Generally, this makes everything modular and extensible and gets rid of the hard-coded `switch (...)` stuff. Test Plan: Ran `arc get-config`, it sort of almost worked. Reviewers: amckinley Reviewed By: amckinley Maniphest Tasks: T13098 Differential Revision: https://secure.phabricator.com/D19695 --- src/__phutil_library_map__.php | 34 ++-- ...rcanistArcConfigurationEngineExtension.php | 6 - ...istConduitConfigurationEngineExtension.php | 6 - src/config/ArcanistConfigOption.php | 6 - src/config/ArcanistConfigurationEngine.php | 117 ++++++++++++ .../ArcanistConfigurationEngineExtension.php | 6 +- .../ArcanistConfigurationSourceList.php | 119 ++++++++++++ .../ArcanistConfigurationSourceValue.php | 24 +++ ...rcanistArcConfigurationEngineExtension.php | 166 +++++++++++++++++ src/config/option/ArcanistConfigOption.php | 88 +++++++++ .../option/ArcanistScalarConfigOption.php | 19 ++ .../option/ArcanistStringConfigOption.php | 18 ++ .../option/ArcanistWildConfigOption.php | 35 ++++ .../source/ArcanistConfigurationSource.php | 23 +++ .../ArcanistDefaultsConfigurationSource.php | 13 +- .../ArcanistDictionaryConfigurationSource.php | 32 ++++ .../ArcanistFileConfigurationSource.php | 5 + .../ArcanistFilesystemConfigurationSource.php | 28 ++- .../ArcanistLocalConfigurationSource.php | 4 + .../ArcanistProjectConfigurationSource.php | 4 + .../ArcanistRuntimeConfigurationSource.php | 45 ++++- .../ArcanistSystemConfigurationSource.php | 4 + .../ArcanistUserConfigurationSource.php | 4 + src/toolset/ArcanistWorkflow.php | 23 ++- .../{ => workflow}/ArcanistAliasWorkflow.php | 0 .../workflow/ArcanistGetConfigWorkflow.php | 137 ++++++++++++++ .../{ => workflow}/ArcanistHelpWorkflow.php | 0 .../workflow/ArcanistSetConfigWorkflow.php | 0 .../ArcanistShellCompleteWorkflow.php | 0 .../ArcanistVersionWorkflow.php | 0 src/workflow/ArcanistGetConfigWorkflow.php | 173 ------------------ support/ArcanistRuntime.php | 17 +- 32 files changed, 945 insertions(+), 211 deletions(-) delete mode 100644 src/config/ArcanistArcConfigurationEngineExtension.php delete mode 100644 src/config/ArcanistConduitConfigurationEngineExtension.php delete mode 100644 src/config/ArcanistConfigOption.php create mode 100644 src/config/ArcanistConfigurationSourceValue.php create mode 100644 src/config/arc/ArcanistArcConfigurationEngineExtension.php create mode 100644 src/config/option/ArcanistConfigOption.php create mode 100644 src/config/option/ArcanistScalarConfigOption.php create mode 100644 src/config/option/ArcanistStringConfigOption.php create mode 100644 src/config/option/ArcanistWildConfigOption.php create mode 100644 src/config/source/ArcanistDictionaryConfigurationSource.php rename src/toolset/{ => workflow}/ArcanistAliasWorkflow.php (100%) create mode 100644 src/toolset/workflow/ArcanistGetConfigWorkflow.php rename src/toolset/{ => workflow}/ArcanistHelpWorkflow.php (100%) rename src/{ => toolset}/workflow/ArcanistSetConfigWorkflow.php (100%) rename src/toolset/{ => workflow}/ArcanistShellCompleteWorkflow.php (100%) rename src/toolset/{ => workflow}/ArcanistVersionWorkflow.php (100%) delete mode 100644 src/workflow/ArcanistGetConfigWorkflow.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 49b78b6a..1ac7b962 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -51,10 +51,10 @@ phutil_register_library_map(array( 'ArcanistAbstractPrivateMethodXHPASTLinterRuleTestCase' => 'lint/linter/xhpast/rules/__tests__/ArcanistAbstractPrivateMethodXHPASTLinterRuleTestCase.php', 'ArcanistAliasFunctionXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistAliasFunctionXHPASTLinterRule.php', 'ArcanistAliasFunctionXHPASTLinterRuleTestCase' => 'lint/linter/xhpast/rules/__tests__/ArcanistAliasFunctionXHPASTLinterRuleTestCase.php', - 'ArcanistAliasWorkflow' => 'toolset/ArcanistAliasWorkflow.php', + 'ArcanistAliasWorkflow' => 'toolset/workflow/ArcanistAliasWorkflow.php', 'ArcanistAmendWorkflow' => 'workflow/ArcanistAmendWorkflow.php', 'ArcanistAnoidWorkflow' => 'workflow/ArcanistAnoidWorkflow.php', - 'ArcanistArcConfigurationEngineExtension' => 'config/ArcanistArcConfigurationEngineExtension.php', + 'ArcanistArcConfigurationEngineExtension' => 'config/arc/ArcanistArcConfigurationEngineExtension.php', 'ArcanistArcToolset' => 'toolset/ArcanistArcToolset.php', 'ArcanistArcWorkflow' => 'workflow/ArcanistArcWorkflow.php', 'ArcanistArrayCombineXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistArrayCombineXHPASTLinterRule.php', @@ -134,9 +134,8 @@ phutil_register_library_map(array( 'ArcanistConcatenationOperatorXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistConcatenationOperatorXHPASTLinterRule.php', 'ArcanistConcatenationOperatorXHPASTLinterRuleTestCase' => 'lint/linter/xhpast/rules/__tests__/ArcanistConcatenationOperatorXHPASTLinterRuleTestCase.php', 'ArcanistConduitCall' => 'conduit/ArcanistConduitCall.php', - 'ArcanistConduitConfigurationEngineExtension' => 'config/ArcanistConduitConfigurationEngineExtension.php', 'ArcanistConduitEngine' => 'conduit/ArcanistConduitEngine.php', - 'ArcanistConfigOption' => 'config/ArcanistConfigOption.php', + 'ArcanistConfigOption' => 'config/option/ArcanistConfigOption.php', 'ArcanistConfigurationDrivenLintEngine' => 'lint/engine/ArcanistConfigurationDrivenLintEngine.php', 'ArcanistConfigurationDrivenUnitTestEngine' => 'unit/engine/ArcanistConfigurationDrivenUnitTestEngine.php', 'ArcanistConfigurationEngine' => 'config/ArcanistConfigurationEngine.php', @@ -144,6 +143,7 @@ phutil_register_library_map(array( 'ArcanistConfigurationManager' => 'configuration/ArcanistConfigurationManager.php', 'ArcanistConfigurationSource' => 'config/source/ArcanistConfigurationSource.php', 'ArcanistConfigurationSourceList' => 'config/ArcanistConfigurationSourceList.php', + 'ArcanistConfigurationSourceValue' => 'config/ArcanistConfigurationSourceValue.php', 'ArcanistConsoleLintRenderer' => 'lint/renderer/ArcanistConsoleLintRenderer.php', 'ArcanistConsoleLintRendererTestCase' => 'lint/renderer/__tests__/ArcanistConsoleLintRendererTestCase.php', 'ArcanistConstructorParenthesesXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistConstructorParenthesesXHPASTLinterRule.php', @@ -164,6 +164,7 @@ phutil_register_library_map(array( 'ArcanistDefaultsConfigurationSource' => 'config/source/ArcanistDefaultsConfigurationSource.php', 'ArcanistDeprecationXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistDeprecationXHPASTLinterRule.php', 'ArcanistDeprecationXHPASTLinterRuleTestCase' => 'lint/linter/xhpast/rules/__tests__/ArcanistDeprecationXHPASTLinterRuleTestCase.php', + 'ArcanistDictionaryConfigurationSource' => 'config/source/ArcanistDictionaryConfigurationSource.php', 'ArcanistDiffByteSizeException' => 'exception/ArcanistDiffByteSizeException.php', 'ArcanistDiffChange' => 'parser/diff/ArcanistDiffChange.php', 'ArcanistDiffChangeType' => 'parser/diff/ArcanistDiffChangeType.php', @@ -217,7 +218,7 @@ phutil_register_library_map(array( 'ArcanistFutureLinter' => 'lint/linter/ArcanistFutureLinter.php', 'ArcanistGeneratedLinter' => 'lint/linter/ArcanistGeneratedLinter.php', 'ArcanistGeneratedLinterTestCase' => 'lint/linter/__tests__/ArcanistGeneratedLinterTestCase.php', - 'ArcanistGetConfigWorkflow' => 'workflow/ArcanistGetConfigWorkflow.php', + 'ArcanistGetConfigWorkflow' => 'toolset/workflow/ArcanistGetConfigWorkflow.php', 'ArcanistGitAPI' => 'repository/api/ArcanistGitAPI.php', 'ArcanistGitCommitMessageHardpointLoader' => 'loader/ArcanistGitCommitMessageHardpointLoader.php', 'ArcanistGitHardpointLoader' => 'loader/ArcanistGitHardpointLoader.php', @@ -234,7 +235,7 @@ phutil_register_library_map(array( 'ArcanistHLintLinter' => 'lint/linter/ArcanistHLintLinter.php', 'ArcanistHLintLinterTestCase' => 'lint/linter/__tests__/ArcanistHLintLinterTestCase.php', 'ArcanistHardpointLoader' => 'loader/ArcanistHardpointLoader.php', - 'ArcanistHelpWorkflow' => 'toolset/ArcanistHelpWorkflow.php', + 'ArcanistHelpWorkflow' => 'toolset/workflow/ArcanistHelpWorkflow.php', 'ArcanistHexadecimalNumericScalarCasingXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistHexadecimalNumericScalarCasingXHPASTLinterRule.php', 'ArcanistHexadecimalNumericScalarCasingXHPASTLinterRuleTestCase' => 'lint/linter/xhpast/rules/__tests__/ArcanistHexadecimalNumericScalarCasingXHPASTLinterRuleTestCase.php', 'ArcanistHgClientChannel' => 'hgdaemon/ArcanistHgClientChannel.php', @@ -405,6 +406,7 @@ phutil_register_library_map(array( 'ArcanistRubyLinter' => 'lint/linter/ArcanistRubyLinter.php', 'ArcanistRubyLinterTestCase' => 'lint/linter/__tests__/ArcanistRubyLinterTestCase.php', 'ArcanistRuntimeConfigurationSource' => 'config/source/ArcanistRuntimeConfigurationSource.php', + 'ArcanistScalarConfigOption' => 'config/option/ArcanistScalarConfigOption.php', 'ArcanistScriptAndRegexLinter' => 'lint/linter/ArcanistScriptAndRegexLinter.php', 'ArcanistSelfClassReferenceXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistSelfClassReferenceXHPASTLinterRule.php', 'ArcanistSelfClassReferenceXHPASTLinterRuleTestCase' => 'lint/linter/xhpast/rules/__tests__/ArcanistSelfClassReferenceXHPASTLinterRuleTestCase.php', @@ -412,10 +414,10 @@ phutil_register_library_map(array( 'ArcanistSelfMemberReferenceXHPASTLinterRuleTestCase' => 'lint/linter/xhpast/rules/__tests__/ArcanistSelfMemberReferenceXHPASTLinterRuleTestCase.php', 'ArcanistSemicolonSpacingXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistSemicolonSpacingXHPASTLinterRule.php', 'ArcanistSemicolonSpacingXHPASTLinterRuleTestCase' => 'lint/linter/xhpast/rules/__tests__/ArcanistSemicolonSpacingXHPASTLinterRuleTestCase.php', - 'ArcanistSetConfigWorkflow' => 'workflow/ArcanistSetConfigWorkflow.php', + 'ArcanistSetConfigWorkflow' => 'toolset/workflow/ArcanistSetConfigWorkflow.php', 'ArcanistSetting' => 'configuration/ArcanistSetting.php', 'ArcanistSettings' => 'configuration/ArcanistSettings.php', - 'ArcanistShellCompleteWorkflow' => 'toolset/ArcanistShellCompleteWorkflow.php', + 'ArcanistShellCompleteWorkflow' => 'toolset/workflow/ArcanistShellCompleteWorkflow.php', 'ArcanistSingleLintEngine' => 'lint/engine/ArcanistSingleLintEngine.php', 'ArcanistSlownessXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistSlownessXHPASTLinterRule.php', 'ArcanistSlownessXHPASTLinterRuleTestCase' => 'lint/linter/xhpast/rules/__tests__/ArcanistSlownessXHPASTLinterRuleTestCase.php', @@ -425,6 +427,7 @@ phutil_register_library_map(array( 'ArcanistStaticThisXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistStaticThisXHPASTLinterRule.php', 'ArcanistStaticThisXHPASTLinterRuleTestCase' => 'lint/linter/xhpast/rules/__tests__/ArcanistStaticThisXHPASTLinterRuleTestCase.php', 'ArcanistStopWorkflow' => 'workflow/ArcanistStopWorkflow.php', + 'ArcanistStringConfigOption' => 'config/option/ArcanistStringConfigOption.php', 'ArcanistSubversionAPI' => 'repository/api/ArcanistSubversionAPI.php', 'ArcanistSubversionWorkingCopy' => 'workingcopy/ArcanistSubversionWorkingCopy.php', 'ArcanistSummaryLintRenderer' => 'lint/renderer/ArcanistSummaryLintRenderer.php', @@ -483,9 +486,10 @@ phutil_register_library_map(array( 'ArcanistVariableReferenceSpacingXHPASTLinterRuleTestCase' => 'lint/linter/xhpast/rules/__tests__/ArcanistVariableReferenceSpacingXHPASTLinterRuleTestCase.php', 'ArcanistVariableVariableXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistVariableVariableXHPASTLinterRule.php', 'ArcanistVariableVariableXHPASTLinterRuleTestCase' => 'lint/linter/xhpast/rules/__tests__/ArcanistVariableVariableXHPASTLinterRuleTestCase.php', - 'ArcanistVersionWorkflow' => 'toolset/ArcanistVersionWorkflow.php', + 'ArcanistVersionWorkflow' => 'toolset/workflow/ArcanistVersionWorkflow.php', 'ArcanistWeldWorkflow' => 'workflow/ArcanistWeldWorkflow.php', 'ArcanistWhichWorkflow' => 'workflow/ArcanistWhichWorkflow.php', + 'ArcanistWildConfigOption' => 'config/option/ArcanistWildConfigOption.php', 'ArcanistWorkflow' => 'toolset/ArcanistWorkflow.php', 'ArcanistWorkingCopy' => 'workingcopy/ArcanistWorkingCopy.php', 'ArcanistWorkingCopyConfigurationSource' => 'config/source/ArcanistWorkingCopyConfigurationSource.php', @@ -1226,7 +1230,6 @@ phutil_register_library_map(array( 'ArcanistConcatenationOperatorXHPASTLinterRule' => 'ArcanistXHPASTLinterRule', 'ArcanistConcatenationOperatorXHPASTLinterRuleTestCase' => 'ArcanistXHPASTLinterRuleTestCase', 'ArcanistConduitCall' => 'Phobject', - 'ArcanistConduitConfigurationEngineExtension' => 'ArcanistConfigurationEngineExtension', 'ArcanistConduitEngine' => 'Phobject', 'ArcanistConfigOption' => 'Phobject', 'ArcanistConfigurationDrivenLintEngine' => 'ArcanistLintEngine', @@ -1236,6 +1239,7 @@ phutil_register_library_map(array( 'ArcanistConfigurationManager' => 'Phobject', 'ArcanistConfigurationSource' => 'Phobject', 'ArcanistConfigurationSourceList' => 'Phobject', + 'ArcanistConfigurationSourceValue' => 'Phobject', 'ArcanistConsoleLintRenderer' => 'ArcanistLintRenderer', 'ArcanistConsoleLintRendererTestCase' => 'PhutilTestCase', 'ArcanistConstructorParenthesesXHPASTLinterRule' => 'ArcanistXHPASTLinterRule', @@ -1253,9 +1257,10 @@ phutil_register_library_map(array( 'ArcanistDeclarationParenthesesXHPASTLinterRuleTestCase' => 'ArcanistXHPASTLinterRuleTestCase', 'ArcanistDefaultParametersXHPASTLinterRule' => 'ArcanistXHPASTLinterRule', 'ArcanistDefaultParametersXHPASTLinterRuleTestCase' => 'ArcanistXHPASTLinterRuleTestCase', - 'ArcanistDefaultsConfigurationSource' => 'ArcanistConfigurationSource', + 'ArcanistDefaultsConfigurationSource' => 'ArcanistDictionaryConfigurationSource', 'ArcanistDeprecationXHPASTLinterRule' => 'ArcanistXHPASTLinterRule', 'ArcanistDeprecationXHPASTLinterRuleTestCase' => 'ArcanistXHPASTLinterRuleTestCase', + 'ArcanistDictionaryConfigurationSource' => 'ArcanistConfigurationSource', 'ArcanistDiffByteSizeException' => 'Exception', 'ArcanistDiffChange' => 'Phobject', 'ArcanistDiffChangeType' => 'Phobject', @@ -1298,7 +1303,7 @@ phutil_register_library_map(array( 'ArcanistFileUploader' => 'Phobject', 'ArcanistFilenameLinter' => 'ArcanistLinter', 'ArcanistFilenameLinterTestCase' => 'ArcanistLinterTestCase', - 'ArcanistFilesystemConfigurationSource' => 'ArcanistConfigurationSource', + 'ArcanistFilesystemConfigurationSource' => 'ArcanistDictionaryConfigurationSource', 'ArcanistFlagWorkflow' => 'ArcanistWorkflow', 'ArcanistFlake8Linter' => 'ArcanistExternalLinter', 'ArcanistFlake8LinterTestCase' => 'ArcanistExternalLinterTestCase', @@ -1496,7 +1501,8 @@ phutil_register_library_map(array( 'ArcanistRuboCopLinterTestCase' => 'ArcanistExternalLinterTestCase', 'ArcanistRubyLinter' => 'ArcanistExternalLinter', 'ArcanistRubyLinterTestCase' => 'ArcanistExternalLinterTestCase', - 'ArcanistRuntimeConfigurationSource' => 'ArcanistConfigurationSource', + 'ArcanistRuntimeConfigurationSource' => 'ArcanistDictionaryConfigurationSource', + 'ArcanistScalarConfigOption' => 'ArcanistConfigOption', 'ArcanistScriptAndRegexLinter' => 'ArcanistLinter', 'ArcanistSelfClassReferenceXHPASTLinterRule' => 'ArcanistXHPASTLinterRule', 'ArcanistSelfClassReferenceXHPASTLinterRuleTestCase' => 'ArcanistXHPASTLinterRuleTestCase', @@ -1517,6 +1523,7 @@ phutil_register_library_map(array( 'ArcanistStaticThisXHPASTLinterRule' => 'ArcanistXHPASTLinterRule', 'ArcanistStaticThisXHPASTLinterRuleTestCase' => 'ArcanistXHPASTLinterRuleTestCase', 'ArcanistStopWorkflow' => 'ArcanistPhrequentWorkflow', + 'ArcanistStringConfigOption' => 'ArcanistScalarConfigOption', 'ArcanistSubversionAPI' => 'ArcanistRepositoryAPI', 'ArcanistSubversionWorkingCopy' => 'ArcanistWorkingCopy', 'ArcanistSummaryLintRenderer' => 'ArcanistLintRenderer', @@ -1578,6 +1585,7 @@ phutil_register_library_map(array( 'ArcanistVersionWorkflow' => 'ArcanistWorkflow', 'ArcanistWeldWorkflow' => 'ArcanistWorkflow', 'ArcanistWhichWorkflow' => 'ArcanistWorkflow', + 'ArcanistWildConfigOption' => 'ArcanistConfigOption', 'ArcanistWorkflow' => 'Phobject', 'ArcanistWorkingCopy' => 'Phobject', 'ArcanistWorkingCopyConfigurationSource' => 'ArcanistFilesystemConfigurationSource', diff --git a/src/config/ArcanistArcConfigurationEngineExtension.php b/src/config/ArcanistArcConfigurationEngineExtension.php deleted file mode 100644 index cc87fe23..00000000 --- a/src/config/ArcanistArcConfigurationEngineExtension.php +++ /dev/null @@ -1,6 +0,0 @@ -workingCopy = $working_copy; @@ -95,4 +96,120 @@ final class ArcanistConfigurationEngine } } + public function newDefaults() { + $map = $this->newConfigOptionsMap(); + return mpull($map, 'getDefaultValue'); + } + + public function newConfigOptionsMap() { + $extensions = $this->newEngineExtensions(); + + $map = array(); + $alias_map = array(); + foreach ($extensions as $extension) { + $options = $extension->newConfigurationOptions(); + + foreach ($options as $option) { + $key = $option->getKey(); + + $this->validateConfigOptionKey($key, $extension); + + if (isset($map[$key])) { + throw new Exception( + pht( + 'Configuration option ("%s") defined by extension "%s" '. + 'conflicts with an existing option. Each option must have '. + 'a unique key.', + $key, + get_class($extension))); + } + + if (isset($alias_map[$key])) { + throw new Exception( + pht( + 'Configuration option ("%s") defined by extension "%s" '. + 'conflicts with an alias for another option ("%s"). The '. + 'key and aliases of each option must be unique.', + $key, + get_class($extension), + $alias_map[$key]->getKey())); + } + + $map[$key] = $option; + + foreach ($option->getAliases() as $alias) { + $this->validateConfigOptionKey($alias, $extension, $key); + + if (isset($map[$alias])) { + throw new Exception( + pht( + 'Configuration option ("%s") defined by extension "%s" '. + 'has an alias ("%s") which conflicts with an existing '. + 'option. The key and aliases of each option must be '. + 'unique.', + $key, + get_class($extension), + $alias)); + } + + if (isset($alias_map[$alias])) { + throw new Exception( + pht( + 'Configuration option ("%s") defined by extension "%s" '. + 'has an alias ("%s") which conflicts with the alias of '. + 'another configuration option ("%s"). The key and aliases '. + 'of each option must be unique.', + $key, + get_class($extension), + $alias, + $alias_map[$alias]->getKey())); + } + + $alias_map[$alias] = $option; + } + } + } + + return $map; + } + + private function validateConfigOptionKey( + $key, + ArcanistConfigurationEngineExtension $extension, + $is_alias_of = null) { + + $is_ok = preg_match('(^[a-z][a-z0-9._-]{2,}\z)', $key); + if (!$is_ok) { + if ($is_alias_of === null) { + throw new Exception( + pht( + 'Extension ("%s") defines invalid configuration with key "%s". '. + 'Configuration keys: may only contain lowercase letters, '. + 'numbers, hyphens, underscores, and periods; must start with a '. + 'letter; and must be at least three characters long.', + get_class($extension), + $key)); + } else { + throw new Exception( + pht( + 'Extension ("%s") defines invalid alias ("%s") for configuration '. + 'key ("%s"). Configuration keys and aliases: may only contain '. + 'lowercase letters, numbers, hyphens, underscores, and periods; '. + 'must start with a letter; and must be at least three characters '. + 'long.', + get_class($extension), + $key, + $is_alias_of)); + } + } + } + + private function newEngineExtensions() { + return id(new PhutilClassMapQuery()) + ->setAncestorClass('ArcanistConfigurationEngineExtension') + ->setUniqueMethod('getExtensionKey') + ->setContinueOnFailure(true) + ->execute(); + } + } diff --git a/src/config/ArcanistConfigurationEngineExtension.php b/src/config/ArcanistConfigurationEngineExtension.php index 77d00843..26566b93 100644 --- a/src/config/ArcanistConfigurationEngineExtension.php +++ b/src/config/ArcanistConfigurationEngineExtension.php @@ -1,6 +1,10 @@ getPhobjectClassConstant('EXTENSIONKEY'); + } + } diff --git a/src/config/ArcanistConfigurationSourceList.php b/src/config/ArcanistConfigurationSourceList.php index 54166742..20fb3e08 100644 --- a/src/config/ArcanistConfigurationSourceList.php +++ b/src/config/ArcanistConfigurationSourceList.php @@ -4,10 +4,129 @@ final class ArcanistConfigurationSourceList extends Phobject { private $sources = array(); + private $configOptions; public function addSource(ArcanistConfigurationSource $source) { $this->sources[] = $source; return $this; } + public function getSources() { + return $this->sources; + } + + public function getConfig($key) { + $option = $this->getConfigOption($key); + $values = $this->getStorageValueList($key); + return $option->getValueFromStorageValueList($values); + } + + public function getStorageValueList($key) { + $values = array(); + + foreach ($this->getSources() as $source) { + if ($source->hasValueForKey($key)) { + $value = $source->getValueForKey($key); + $values[] = new ArcanistConfigurationSourceValue( + $source, + $source->getValueForKey($key)); + } + } + + return $values; + } + + public function getConfigOption($key) { + $options = $this->getConfigOptions(); + + if (!isset($options[$key])) { + throw new Exception( + pht( + 'Configuration option ("%s") is unrecognized. You can only read '. + 'recognized configuration options.', + $key)); + } + + return $options[$key]; + } + + public function setConfigOptions(array $config_options) { + assert_instances_of($config_options, 'ArcanistConfigOption'); + + $config_options = mpull($config_options, null, 'getKey'); + $this->configOptions = $config_options; + + return $this; + } + + public function getConfigOptions() { + if ($this->configOptions === null) { + throw new PhutilInvalidStateException('setConfigOptions'); + } + + return $this->configOptions; + } + + public function validateConfiguration() { + $options = $this->getConfigOptions(); + + $aliases = array(); + foreach ($options as $key => $option) { + foreach ($option->getAliases() as $alias) { + $aliases[$alias] = $key; + } + } + + // TOOLSETS: Handle the case where config specifies both a value and an + // alias for that value. The alias should be ignored and we should emit + // a warning. This also needs to be implemented when actually reading + // configuration. + + $value_lists = array(); + foreach ($this->getSources() as $source) { + $keys = $source->getAllKeys(); + foreach ($keys as $key) { + $resolved_key = idx($aliases, $key, $key); + $option = idx($options, $resolved_key); + + // If there's no option object for this config, this value is + // unrecognized. Sources are free to handle this however they want: + // for config files we emit a warning; for "--config" we fatal. + + if (!$option) { + $source->didReadUnknownOption($key); + continue; + } + + $raw_value = $source->getValueForKey($key); + + // Make sure we can convert whatever value the configuration source is + // providing into a legitimate runtime value. + try { + $value = $raw_value; + if ($source->isStringSource()) { + $value = $option->getStorageValueFromStringValue($value); + } + $option->getValueFromStorageValue($value); + + $value_lists[$resolved_key][] = new ArcanistConfigurationSourceValue( + $source, + $raw_value); + } catch (Exception $ex) { + throw $ex; + } + } + } + + // Make sure each value list can be merged. + foreach ($value_lists as $key => $value_list) { + try { + $options[$key]->getValueFromStorageValueList($value_list); + } catch (Exception $ex) { + throw $ex; + } + } + + } + } \ No newline at end of file diff --git a/src/config/ArcanistConfigurationSourceValue.php b/src/config/ArcanistConfigurationSourceValue.php new file mode 100644 index 00000000..512cea55 --- /dev/null +++ b/src/config/ArcanistConfigurationSourceValue.php @@ -0,0 +1,24 @@ +source = $source; + $this->value = $value; + } + + public function getConfigurationSource() { + return $this->source; + } + + public function getValue() { + return $this->value; + } + +} + + diff --git a/src/config/arc/ArcanistArcConfigurationEngineExtension.php b/src/config/arc/ArcanistArcConfigurationEngineExtension.php new file mode 100644 index 00000000..71837a49 --- /dev/null +++ b/src/config/arc/ArcanistArcConfigurationEngineExtension.php @@ -0,0 +1,166 @@ + array( + 'type' => 'list', + 'legacy' => 'phutil_libraries', + 'help' => pht( + 'A list of paths to phutil libraries that should be loaded at '. + 'startup. This can be used to make classes available, like lint '. + 'or unit test engines.'), + 'default' => array(), + 'example' => '["/var/arc/customlib/src"]', + ), + + 'arc.feature.start.default' => array( + 'type' => 'string', + 'help' => pht( + 'The name of the default branch to create the new feature branch '. + 'off of.'), + 'example' => '"develop"', + ), + 'arc.land.onto.default' => array( + 'type' => 'string', + 'help' => pht( + 'The name of the default branch to land changes onto when '. + '`%s` is run.', + 'arc land'), + 'example' => '"develop"', + ), + + 'arc.autostash' => array( + 'type' => 'bool', + 'help' => pht( + 'Whether %s should permit the automatic stashing of changes in the '. + 'working directory when requiring a clean working copy. This option '. + 'should only be used when users understand how to restore their '. + 'working directory from the local stash if an Arcanist operation '. + 'causes an unrecoverable error.', + 'arc'), + 'default' => false, + 'example' => 'false', + ), + + 'aliases' => array( + 'type' => 'aliases', + 'help' => pht( + 'Configured command aliases. Use "arc alias" to define aliases.'), + ), + + 'history.immutable' => array( + 'type' => 'bool', + 'legacy' => 'immutable_history', + 'help' => pht( + 'If true, %s will never change repository history (e.g., through '. + 'amending or rebasing). Defaults to true in Mercurial and false in '. + 'Git. This setting has no effect in Subversion.', + 'arc'), + 'example' => 'false', + ), + 'editor' => array( + 'type' => 'string', + 'help' => pht( + 'Command to use to invoke an interactive editor, like `%s` or `%s`. '. + 'This setting overrides the %s environmental variable.', + 'nano', + 'vim', + 'EDITOR'), + 'example' => '"nano"', + ), + 'https.cabundle' => array( + 'type' => 'string', + 'help' => pht( + "Path to a custom CA bundle file to be used for arcanist's cURL ". + "calls. This is used primarily when your conduit endpoint is ". + "behind HTTPS signed by your organization's internal CA."), + 'example' => 'support/yourca.pem', + ), + 'https.blindly-trust-domains' => array( + 'type' => 'list', + 'help' => pht( + 'List of domains to blindly trust SSL certificates for. '. + 'Disables peer verification.'), + 'default' => array(), + 'example' => '["secure.mycompany.com"]', + ), + 'browser' => array( + 'type' => 'string', + 'help' => pht('Command to use to invoke a web browser.'), + 'example' => '"gnome-www-browser"', + ), + 'http.basicauth.user' => array( + 'type' => 'string', + 'help' => pht('Username to use for basic auth over HTTP transports.'), + 'example' => '"bob"', + ), + 'http.basicauth.pass' => array( + 'type' => 'string', + 'help' => pht('Password to use for basic auth over HTTP transports.'), + 'example' => '"bobhasasecret"', + ), + +*/ + + + + return array( + id(new ArcanistStringConfigOption()) + ->setKey('base') + ->setSummary(pht('Ruleset for selecting commit ranges.')) + ->setHelp( + pht( + 'Base commit ruleset to invoke when determining the start of a '. + 'commit range. See "Arcanist User Guide: Commit Ranges" for '. + 'details.')) + ->setExamples( + array( + 'arc:amended, arc:prompt', + )), + id(new ArcanistStringConfigOption()) + ->setKey('repository') + ->setAliases( + array( + 'repository.callsign', + )) + ->setSummary(pht('Repository for the current working copy.')) + ->setHelp( + pht( + 'Associate the working copy with a specific Phabricator '. + 'repository. Normally, `arc` can figure this association out on '. + 'its own, but if your setup is unusual you can use this option '. + 'to tell it what the desired value is.')) + ->setExamples( + array( + 'libexample', + 'XYZ', + 'R123', + '123', + )), + id(new ArcanistStringConfigOption()) + ->setKey('phabricator.uri') + ->setAliases( + array( + 'conduit_uri', + 'default', + )) + ->setSummary(pht('Phabricator install to connect to.')) + ->setHelp( + pht( + 'Associates this working copy with a specific installation of '. + 'Phabricator.')) + ->setExamples( + array( + 'https://phabricator.mycompany.com/', + )), + ); + } + +} diff --git a/src/config/option/ArcanistConfigOption.php b/src/config/option/ArcanistConfigOption.php new file mode 100644 index 00000000..0372e2b0 --- /dev/null +++ b/src/config/option/ArcanistConfigOption.php @@ -0,0 +1,88 @@ +key = $key; + return $this; + } + + public function getKey() { + return $this->key; + } + + public function setAliases($aliases) { + $this->aliases = $aliases; + return $this; + } + + public function getAliases() { + return $this->aliases; + } + + public function setSummary($summary) { + $this->summary = $summary; + return $this; + } + + public function getSummary() { + return $this->summary; + } + + public function setHelp($help) { + $this->help = $help; + return $this; + } + + public function getHelp() { + return $this->help; + } + + public function setExamples(array $examples) { + $this->examples = $examples; + return $this; + } + + public function getExamples() { + return $this->examples; + } + + public function setDefaultValue($default_value) { + $this->defaultValue = $default_value; + return $this; + } + + public function getDefaultValue() { + return $this->defaultValue; + } + + abstract public function getType(); + + abstract public function getValueFromStorageValueList(array $list); + abstract public function getStorageValueFromStringValue($value); + abstract public function getValueFromStorageValue($value); + abstract public function getDisplayValueFromValue($value); + + protected function getStorageValueFromSourceValue( + ArcanistConfigurationSourceValue $source_value) { + + $value = $source_value->getValue(); + $source = $source_value->getConfigurationSource(); + + if ($source->isStringSource()) { + $value = $this->getStorageValueFromStringValue($value); + } + + return $value; + } + + +} diff --git a/src/config/option/ArcanistScalarConfigOption.php b/src/config/option/ArcanistScalarConfigOption.php new file mode 100644 index 00000000..9ccda1e6 --- /dev/null +++ b/src/config/option/ArcanistScalarConfigOption.php @@ -0,0 +1,19 @@ +getStorageValueFromSourceValue($source_value); + + return $this->getValueFromStorageValue($storage_value); + } + + public function getValueFromStorageValue($value) { + return $value; + } + +} diff --git a/src/config/option/ArcanistStringConfigOption.php b/src/config/option/ArcanistStringConfigOption.php new file mode 100644 index 00000000..29076a4c --- /dev/null +++ b/src/config/option/ArcanistStringConfigOption.php @@ -0,0 +1,18 @@ +getStorageValueFromSourceValue($source_value); + + return $this->getValueFromStorageValue($storage_value); + } + + public function getValueFromStorageValue($value) { + return $value; + } + +} diff --git a/src/config/source/ArcanistConfigurationSource.php b/src/config/source/ArcanistConfigurationSource.php index 47b962a8..49050171 100644 --- a/src/config/source/ArcanistConfigurationSource.php +++ b/src/config/source/ArcanistConfigurationSource.php @@ -3,4 +3,27 @@ abstract class ArcanistConfigurationSource extends Phobject { + abstract public function getSourceDisplayName(); + abstract public function getAllKeys(); + abstract public function hasValueForKey($key); + abstract public function getValueForKey($key); + + public function isStringSource() { + return false; + } + + public function didReadUnknownOption($key) { + // TOOLSETS: Standardize this kind of messaging? On ArcanistRuntime? + + fprintf( + STDERR, + tsprintf( + "** %s ** %s\n", + pht('WARNING'), + pht( + 'Ignoring unrecognized configuration option ("%s") from source: %s.', + $key, + $this->getSourceDisplayName()))); + } + } \ No newline at end of file diff --git a/src/config/source/ArcanistDefaultsConfigurationSource.php b/src/config/source/ArcanistDefaultsConfigurationSource.php index e5c35b9d..4c7bb0e6 100644 --- a/src/config/source/ArcanistDefaultsConfigurationSource.php +++ b/src/config/source/ArcanistDefaultsConfigurationSource.php @@ -1,6 +1,17 @@ newDefaults(); + + parent::__construct($values); + } } \ No newline at end of file diff --git a/src/config/source/ArcanistDictionaryConfigurationSource.php b/src/config/source/ArcanistDictionaryConfigurationSource.php new file mode 100644 index 00000000..590c6110 --- /dev/null +++ b/src/config/source/ArcanistDictionaryConfigurationSource.php @@ -0,0 +1,32 @@ +values = $dictionary; + } + + public function getAllKeys() { + return array_keys($this->values); + } + + public function hasValueForKey($key) { + return array_key_exists($key, $this->values); + } + + public function getValueForKey($key) { + if (!$this->hasValueForKey($key)) { + throw new Exception( + pht( + 'Configuration source ("%s") has no value for key ("%s").', + get_class($this), + $key)); + } + + return $this->values[$key]; + } + +} \ No newline at end of file diff --git a/src/config/source/ArcanistFileConfigurationSource.php b/src/config/source/ArcanistFileConfigurationSource.php index b8671431..22fb1d81 100644 --- a/src/config/source/ArcanistFileConfigurationSource.php +++ b/src/config/source/ArcanistFileConfigurationSource.php @@ -3,4 +3,9 @@ final class ArcanistFileConfigurationSource extends ArcanistConfigurationSource { + public function getFileKindDisplayName() { + return pht('Config File'); + } + + } \ No newline at end of file diff --git a/src/config/source/ArcanistFilesystemConfigurationSource.php b/src/config/source/ArcanistFilesystemConfigurationSource.php index 244506a7..7d9566fb 100644 --- a/src/config/source/ArcanistFilesystemConfigurationSource.php +++ b/src/config/source/ArcanistFilesystemConfigurationSource.php @@ -1,6 +1,32 @@ path = $path; + + $values = array(); + if (Filesystem::pathExists($path)) { + $contents = Filesystem::readFile($path); + if (strlen(trim($contents))) { + $values = phutil_json_decode($contents); + } + } + + parent::__construct($values); + } + + public function getPath() { + return $this->path; + } + + public function getSourceDisplayName() { + return pht('%s (%s)', $this->getFileKindDisplayName(), $this->getPath()); + } + + abstract public function getFileKindDisplayName(); } \ No newline at end of file diff --git a/src/config/source/ArcanistLocalConfigurationSource.php b/src/config/source/ArcanistLocalConfigurationSource.php index 8725ac21..2270effe 100644 --- a/src/config/source/ArcanistLocalConfigurationSource.php +++ b/src/config/source/ArcanistLocalConfigurationSource.php @@ -3,4 +3,8 @@ final class ArcanistLocalConfigurationSource extends ArcanistWorkingCopyConfigurationSource { + public function getFileKindDisplayName() { + return pht('Local Config File'); + } + } \ No newline at end of file diff --git a/src/config/source/ArcanistProjectConfigurationSource.php b/src/config/source/ArcanistProjectConfigurationSource.php index 4ee4be43..9d7019d3 100644 --- a/src/config/source/ArcanistProjectConfigurationSource.php +++ b/src/config/source/ArcanistProjectConfigurationSource.php @@ -3,4 +3,8 @@ final class ArcanistProjectConfigurationSource extends ArcanistWorkingCopyConfigurationSource { + public function getFileKindDisplayName() { + return pht('Project Config File'); + } + } \ No newline at end of file diff --git a/src/config/source/ArcanistRuntimeConfigurationSource.php b/src/config/source/ArcanistRuntimeConfigurationSource.php index cee65e4b..8d3e9731 100644 --- a/src/config/source/ArcanistRuntimeConfigurationSource.php +++ b/src/config/source/ArcanistRuntimeConfigurationSource.php @@ -1,6 +1,49 @@ configurationSourceList = $config; + return $this; + } + + final public function getConfigurationSourceList() { + return $this->configurationSourceList; + } + + final public function setConfigurationEngine( + ArcanistConfigurationEngine $configuration_engine) { + $this->configurationEngine = $configuration_engine; + return $this; + } + + final public function getConfigurationEngine() { + return $this->configurationEngine; + } + final protected function getToolsetKey() { return $this->getToolset()->getToolsetKey(); } diff --git a/src/toolset/ArcanistAliasWorkflow.php b/src/toolset/workflow/ArcanistAliasWorkflow.php similarity index 100% rename from src/toolset/ArcanistAliasWorkflow.php rename to src/toolset/workflow/ArcanistAliasWorkflow.php diff --git a/src/toolset/workflow/ArcanistGetConfigWorkflow.php b/src/toolset/workflow/ArcanistGetConfigWorkflow.php new file mode 100644 index 00000000..5fb51052 --- /dev/null +++ b/src/toolset/workflow/ArcanistGetConfigWorkflow.php @@ -0,0 +1,137 @@ + array( + 'help' => pht('Show detailed information about options.'), + ), + '*' => 'argv', + ); + } + + public function runWorkflow() { + $argv = $this->getArgument('argv'); + $is_verbose = $this->getArgument('verbose'); + + $source_list = $this->getConfigurationSourceList(); + $config_engine = $this->getConfigurationEngine(); + + $options_map = $config_engine->newConfigOptionsMap(); + + $all_keys = array(); + $alias_map = array(); + foreach ($options_map as $key => $config_option) { + $all_keys[$key] = $key; + foreach ($config_option->getAliases() as $alias) { + $alias_map[$alias] = $key; + } + } + + foreach ($source_list->getSources() as $source) { + foreach ($source->getAllKeys() as $key) { + $all_keys[$key] = $key; + } + } + + ksort($all_keys); + + $defaults_map = $config_engine->newDefaults(); + + foreach ($all_keys as $key) { + $option = idx($options_map, $key); + + if ($option) { + $option_summary = $option->getSummary(); + $option_help = $option->getHelp(); + } else { + $option_summary = pht('(This option is unrecognized.)'); + $option_help = $option_summary; + } + + if ($option) { + $formatter = $option; + } else { + $formatter = new ArcanistWildConfigOption(); + } + + if (!$is_verbose) { + echo tsprintf( + "**%s**\n%R\n\n", + $key, + $option_summary); + } else { + echo tsprintf( + "**%s**\n\n%R\n\n", + $key, + $option_help); + } + + // NOTE: We can only get configuration from a SourceList if the option is + // a recognized option, so skip this part if the option isn't known. + if ($option) { + $value = $source_list->getConfig($key); + $display_value = $formatter->getDisplayValueFromValue($value); + + echo tsprintf("%s: %s\n", pht('Value'), $display_value); + + $default_value = idx($defaults_map, $key); + $display_default = $formatter->getDisplayValueFromValue($value); + + echo tsprintf("%s: %s\n", pht('Default'), $display_default); + } + + foreach ($source_list->getSources() as $source) { + if ($source->hasValueForKey($key)) { + $source_value = $source->getValueForKey($key); + $source_value = $formatter->getValueFromStorageValue($source_value); + $source_display = $formatter->getDisplayValueFromValue($source_value); + } else { + $source_display = pht('-'); + } + + echo tsprintf( + "%s: %s\n", + $source->getSourceDisplayName(), + $source_display); + } + } + + // if (!$verbose) { + // $console->writeOut( + // "(%s)\n", + // pht('Run with %s for more details.', '--verbose')); + // } + + return 0; + } + +} diff --git a/src/toolset/ArcanistHelpWorkflow.php b/src/toolset/workflow/ArcanistHelpWorkflow.php similarity index 100% rename from src/toolset/ArcanistHelpWorkflow.php rename to src/toolset/workflow/ArcanistHelpWorkflow.php diff --git a/src/workflow/ArcanistSetConfigWorkflow.php b/src/toolset/workflow/ArcanistSetConfigWorkflow.php similarity index 100% rename from src/workflow/ArcanistSetConfigWorkflow.php rename to src/toolset/workflow/ArcanistSetConfigWorkflow.php diff --git a/src/toolset/ArcanistShellCompleteWorkflow.php b/src/toolset/workflow/ArcanistShellCompleteWorkflow.php similarity index 100% rename from src/toolset/ArcanistShellCompleteWorkflow.php rename to src/toolset/workflow/ArcanistShellCompleteWorkflow.php diff --git a/src/toolset/ArcanistVersionWorkflow.php b/src/toolset/workflow/ArcanistVersionWorkflow.php similarity index 100% rename from src/toolset/ArcanistVersionWorkflow.php rename to src/toolset/workflow/ArcanistVersionWorkflow.php diff --git a/src/workflow/ArcanistGetConfigWorkflow.php b/src/workflow/ArcanistGetConfigWorkflow.php deleted file mode 100644 index 565856bd..00000000 --- a/src/workflow/ArcanistGetConfigWorkflow.php +++ /dev/null @@ -1,173 +0,0 @@ - array( - 'help' => pht('Show detailed information about options.'), - ), - '*' => 'argv', - ); - } - - public function desiresRepositoryAPI() { - return true; - } - - public function run() { - $argv = $this->getArgument('argv'); - $verbose = $this->getArgument('verbose'); - - $settings = new ArcanistSettings(); - - $configuration_manager = $this->getConfigurationManager(); - $configs = array( - ArcanistConfigurationManager::CONFIG_SOURCE_LOCAL => - $configuration_manager->readLocalArcConfig(), - ArcanistConfigurationManager::CONFIG_SOURCE_PROJECT => - $this->getWorkingCopy()->readProjectConfig(), - ArcanistConfigurationManager::CONFIG_SOURCE_USER => - $configuration_manager->readUserArcConfig(), - ArcanistConfigurationManager::CONFIG_SOURCE_SYSTEM => - $configuration_manager->readSystemArcConfig(), - ArcanistConfigurationManager::CONFIG_SOURCE_DEFAULT => - $configuration_manager->readDefaultConfig(), - ); - - if ($argv) { - $keys = $argv; - } else { - $keys = array_mergev(array_map('array_keys', $configs)); - $keys = array_merge($keys, $settings->getAllKeys()); - $keys = array_unique($keys); - sort($keys); - } - - $console = PhutilConsole::getConsole(); - $multi = (count($keys) > 1); - - foreach ($keys as $key) { - $console->writeOut("**%s**\n\n", $key); - - if ($verbose) { - $help = $settings->getHelp($key); - if (!$help) { - $help = pht( - '(This configuration value is not recognized by arc. It may '. - 'be misspelled or out of date.)'); - } - - $console->writeOut("%s\n\n", phutil_console_wrap($help, 4)); - - $console->writeOut( - "%s: %s\n\n", - sprintf('% 20.20s', pht('Example Value')), - $settings->getExample($key)); - - } - - $values = array(); - foreach ($configs as $config_key => $config) { - if (array_key_exists($key, $config)) { - $values[$config_key] = $config[$key]; - } else { - // If we didn't find a value, look for a legacy value. - $source_project = ArcanistConfigurationManager::CONFIG_SOURCE_PROJECT; - if ($config_key === $source_project) { - $legacy_name = $settings->getLegacyName($key); - if (array_key_exists($legacy_name, $config)) { - $values[$config_key] = $config[$legacy_name]; - } - } - } - } - - $console->writeOut( - '%s: ', - sprintf('% 20.20s', pht('Current Value'))); - - if ($values) { - $value = head($values); - $value = $settings->formatConfigValueForDisplay($key, $value); - $console->writeOut("%s\n", $value); - } else { - $console->writeOut("-\n"); - } - - $console->writeOut( - '%s: ', - sprintf('% 20.20s', pht('Current Source'))); - - if ($values) { - $source = head_key($values); - $console->writeOut("%s\n", $source); - } else { - $console->writeOut("-\n"); - } - - if ($verbose) { - $console->writeOut("\n"); - - foreach ($configs as $name => $config) { - $have_value = false; - if (array_key_exists($name, $values)) { - $have_value = true; - $value = $values[$name]; - } - - $console->writeOut( - '%s: ', - sprintf('% 20.20s', pht('%s Value', $name))); - - if ($have_value) { - $console->writeOut( - "%s\n", - $settings->formatConfigValueForDisplay($key, $value)); - } else { - $console->writeOut("-\n"); - } - } - } - - if ($multi) { - echo "\n"; - } - } - - if (!$verbose) { - $console->writeOut( - "(%s)\n", - pht('Run with %s for more details.', '--verbose')); - } - - return 0; - } - -} diff --git a/support/ArcanistRuntime.php b/support/ArcanistRuntime.php index 30a0c570..5121fc9d 100644 --- a/support/ArcanistRuntime.php +++ b/support/ArcanistRuntime.php @@ -65,10 +65,17 @@ final class ArcanistRuntime { $args->parsePartial($config_args, true); - $config = $this->loadConfiguration($args); + $config_engine = $this->loadConfiguration($args); + $config = $config_engine->newConfigurationSourceList(); $this->loadLibraries($args, $config); + // Now that we've loaded libraries, we can validate configuration. + // Do this before continuing since configuration can impact other + // behaviors immediately and we want to catch any issues right away. + $config->setConfigOptions($config_engine->newConfigOptionsMap()); + $config->validateConfiguration(); + $toolset = $this->newToolset($argv); $args->parsePartial($toolset->getToolsetArguments()); @@ -78,6 +85,10 @@ final class ArcanistRuntime { $phutil_workflows = array(); foreach ($workflows as $key => $workflow) { $phutil_workflows[$key] = $workflow->newPhutilWorkflow(); + + $workflow + ->setConfigurationEngine($config_engine) + ->setConfigurationSourceList($config); } $unconsumed_argv = $args->getUnconsumedArgumentVector(); @@ -218,7 +229,7 @@ final class ArcanistRuntime { $engine->setWorkingCopy($working_copy); } - return $engine->newConfigurationSourceList(); + return $engine; } private function loadLibraries( @@ -462,6 +473,8 @@ final class ArcanistRuntime { array $argv, ArcanistConfigurationSourceList $config) { + return $argv; + $command = head($argv); // If this is a match for a recognized workflow, just return the arguments