Changeset View
Changeset View
Standalone View
Standalone View
src/config/ArcanistConfigurationEngine.php
- This file was added.
| <?php | |||||
| final class ArcanistConfigurationEngine | |||||
| extends Phobject { | |||||
| private $workingCopy; | |||||
| private $arguments; | |||||
| private $toolset; | |||||
| public function setWorkingCopy(ArcanistWorkingCopy $working_copy) { | |||||
| $this->workingCopy = $working_copy; | |||||
| return $this; | |||||
| } | |||||
| public function getWorkingCopy() { | |||||
| return $this->workingCopy; | |||||
| } | |||||
| public function setArguments(PhutilArgumentParser $arguments) { | |||||
| $this->arguments = $arguments; | |||||
| return $this; | |||||
| } | |||||
| public function getArguments() { | |||||
| if (!$this->arguments) { | |||||
| throw new PhutilInvalidStateException('setArguments'); | |||||
| } | |||||
| return $this->arguments; | |||||
| } | |||||
| public function newConfigurationSourceList() { | |||||
| $list = new ArcanistConfigurationSourceList(); | |||||
| $list->addSource(new ArcanistDefaultsConfigurationSource()); | |||||
| $arguments = $this->getArguments(); | |||||
| // If the invoker has provided one or more configuration files with | |||||
| // "--config-file" arguments, read those files instead of the system | |||||
| // and user configuration files. Otherwise, read the system and user | |||||
| // configuration files. | |||||
| $config_files = $arguments->getArg('config-file'); | |||||
| if ($config_files) { | |||||
| foreach ($config_files as $config_file) { | |||||
| $list->addSource(new ArcanistFileConfigurationSource($config_file)); | |||||
| } | |||||
| } else { | |||||
| $system_path = $this->getSystemConfigurationFilePath(); | |||||
| $list->addSource(new ArcanistSystemConfigurationSource($system_path)); | |||||
| $user_path = $this->getUserConfigurationFilePath(); | |||||
| $list->addSource(new ArcanistUserConfigurationSource($user_path)); | |||||
| } | |||||
| // If we're running in a working copy, load the ".arcconfig" and any | |||||
| // local configuration. | |||||
| $working_copy = $this->getWorkingCopy(); | |||||
| if ($working_copy) { | |||||
| $project_path = $working_copy->getProjectConfigurationFilePath(); | |||||
| if ($project_path !== null) { | |||||
| $list->addSource(new ArcanistProjectConfigurationSource($project_path)); | |||||
| } | |||||
| $local_path = $working_copy->getLocalConfigurationFilePath(); | |||||
| if ($local_path !== null) { | |||||
| $list->addSource(new ArcanistLocalConfigurationSource($local_path)); | |||||
| } | |||||
| } | |||||
| // If the invoker has provided "--config" arguments, parse those now. | |||||
| $runtime_args = $arguments->getArg('config'); | |||||
| if ($runtime_args) { | |||||
| $list->addSource(new ArcanistRuntimeConfigurationSource($runtime_args)); | |||||
| } | |||||
| return $list; | |||||
| } | |||||
| private function getSystemConfigurationFilePath() { | |||||
| if (phutil_is_windows()) { | |||||
| return Filesystem::resolvePath( | |||||
| 'Phabricator/Arcanist/config', | |||||
| getenv('ProgramData')); | |||||
| } else { | |||||
| return '/etc/arcconfig'; | |||||
| } | |||||
| } | |||||
| private function getUserConfigurationFilePath() { | |||||
| if (phutil_is_windows()) { | |||||
| return getenv('APPDATA').'/.arcrc'; | |||||
| } else { | |||||
| return getenv('HOME').'/.arcrc'; | |||||
| } | |||||
| } | |||||
| 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) { | |||||
| $reserved = array( | |||||
| // The presence of this key is used to detect old "~/.arcrc" files, so | |||||
| // configuration options may not use it. | |||||
| 'config', | |||||
| ); | |||||
| $reserved = array_fuse($reserved); | |||||
| if (isset($reserved[$key])) { | |||||
| throw new Exception( | |||||
| pht( | |||||
| 'Extension ("%s") defines invalid configuration with key "%s". '. | |||||
| 'This key is reserved.', | |||||
| get_class($extension), | |||||
| $key)); | |||||
| } | |||||
| $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(); | |||||
| } | |||||
| } | |||||