1<?php 2 3final class ArcanistConfigurationEngine 4 extends Phobject { 5 6 private $workingCopy; 7 private $arguments; 8 private $toolset; 9 10 public function setWorkingCopy(ArcanistWorkingCopy $working_copy) { 11 $this->workingCopy = $working_copy; 12 return $this; 13 } 14 15 public function getWorkingCopy() { 16 return $this->workingCopy; 17 } 18 19 public function setArguments(PhutilArgumentParser $arguments) { 20 $this->arguments = $arguments; 21 return $this; 22 } 23 24 public function getArguments() { 25 if (!$this->arguments) { 26 throw new PhutilInvalidStateException('setArguments'); 27 } 28 return $this->arguments; 29 } 30 31 public function newConfigurationSourceList() { 32 $list = new ArcanistConfigurationSourceList(); 33 34 $list->addSource(new ArcanistDefaultsConfigurationSource()); 35 36 $arguments = $this->getArguments(); 37 38 // If the invoker has provided one or more configuration files with 39 // "--config-file" arguments, read those files instead of the system 40 // and user configuration files. Otherwise, read the system and user 41 // configuration files. 42 43 $config_files = $arguments->getArg('config-file'); 44 if ($config_files) { 45 foreach ($config_files as $config_file) { 46 $list->addSource(new ArcanistFileConfigurationSource($config_file)); 47 } 48 } else { 49 $system_path = $this->getSystemConfigurationFilePath(); 50 $list->addSource(new ArcanistSystemConfigurationSource($system_path)); 51 52 $user_path = $this->getUserConfigurationFilePath(); 53 $list->addSource(new ArcanistUserConfigurationSource($user_path)); 54 } 55 56 57 // If we're running in a working copy, load the ".arcconfig" and any 58 // local configuration. 59 $working_copy = $this->getWorkingCopy(); 60 if ($working_copy) { 61 $project_path = $working_copy->getProjectConfigurationFilePath(); 62 if ($project_path !== null) { 63 $list->addSource(new ArcanistProjectConfigurationSource($project_path)); 64 } 65 66 $local_path = $working_copy->getLocalConfigurationFilePath(); 67 if ($local_path !== null) { 68 $list->addSource(new ArcanistLocalConfigurationSource($local_path)); 69 } 70 } 71 72 // If the invoker has provided "--config" arguments, parse those now. 73 $runtime_args = $arguments->getArg('config'); 74 if ($runtime_args) { 75 $list->addSource(new ArcanistRuntimeConfigurationSource($runtime_args)); 76 } 77 78 return $list; 79 } 80 81 private function getSystemConfigurationFilePath() { 82 if (phutil_is_windows()) { 83 return Filesystem::resolvePath( 84 'Phabricator/Arcanist/config', 85 getenv('ProgramData')); 86 } else { 87 return '/etc/arcconfig'; 88 } 89 } 90 91 private function getUserConfigurationFilePath() { 92 if (phutil_is_windows()) { 93 return getenv('APPDATA').'/.arcrc'; 94 } else { 95 return getenv('HOME').'/.arcrc'; 96 } 97 } 98 99 public function newDefaults() { 100 $map = $this->newConfigOptionsMap(); 101 return mpull($map, 'getDefaultValue'); 102 } 103 104 public function newConfigOptionsMap() { 105 $extensions = $this->newEngineExtensions(); 106 107 $map = array(); 108 $alias_map = array(); 109 foreach ($extensions as $extension) { 110 $options = $extension->newConfigurationOptions(); 111 112 foreach ($options as $option) { 113 $key = $option->getKey(); 114 115 $this->validateConfigOptionKey($key, $extension); 116 117 if (isset($map[$key])) { 118 throw new Exception( 119 pht( 120 'Configuration option ("%s") defined by extension "%s" '. 121 'conflicts with an existing option. Each option must have '. 122 'a unique key.', 123 $key, 124 get_class($extension))); 125 } 126 127 if (isset($alias_map[$key])) { 128 throw new Exception( 129 pht( 130 'Configuration option ("%s") defined by extension "%s" '. 131 'conflicts with an alias for another option ("%s"). The '. 132 'key and aliases of each option must be unique.', 133 $key, 134 get_class($extension), 135 $alias_map[$key]->getKey())); 136 } 137 138 $map[$key] = $option; 139 140 foreach ($option->getAliases() as $alias) { 141 $this->validateConfigOptionKey($alias, $extension, $key); 142 143 if (isset($map[$alias])) { 144 throw new Exception( 145 pht( 146 'Configuration option ("%s") defined by extension "%s" '. 147 'has an alias ("%s") which conflicts with an existing '. 148 'option. The key and aliases of each option must be '. 149 'unique.', 150 $key, 151 get_class($extension), 152 $alias)); 153 } 154 155 if (isset($alias_map[$alias])) { 156 throw new Exception( 157 pht( 158 'Configuration option ("%s") defined by extension "%s" '. 159 'has an alias ("%s") which conflicts with the alias of '. 160 'another configuration option ("%s"). The key and aliases '. 161 'of each option must be unique.', 162 $key, 163 get_class($extension), 164 $alias, 165 $alias_map[$alias]->getKey())); 166 } 167 168 $alias_map[$alias] = $option; 169 } 170 } 171 } 172 173 return $map; 174 } 175 176 private function validateConfigOptionKey( 177 $key, 178 ArcanistConfigurationEngineExtension $extension, 179 $is_alias_of = null) { 180 181 $reserved = array( 182 // The presence of this key is used to detect old "~/.arcrc" files, so 183 // configuration options may not use it. 184 'config', 185 ); 186 $reserved = array_fuse($reserved); 187 188 if (isset($reserved[$key])) { 189 throw new Exception( 190 pht( 191 'Extension ("%s") defines invalid configuration with key "%s". '. 192 'This key is reserved.', 193 get_class($extension), 194 $key)); 195 } 196 197 $is_ok = preg_match('(^[a-z][a-z0-9._-]{2,}\z)', $key); 198 if (!$is_ok) { 199 if ($is_alias_of === null) { 200 throw new Exception( 201 pht( 202 'Extension ("%s") defines invalid configuration with key "%s". '. 203 'Configuration keys: may only contain lowercase letters, '. 204 'numbers, hyphens, underscores, and periods; must start with a '. 205 'letter; and must be at least three characters long.', 206 get_class($extension), 207 $key)); 208 } else { 209 throw new Exception( 210 pht( 211 'Extension ("%s") defines invalid alias ("%s") for configuration '. 212 'key ("%s"). Configuration keys and aliases: may only contain '. 213 'lowercase letters, numbers, hyphens, underscores, and periods; '. 214 'must start with a letter; and must be at least three characters '. 215 'long.', 216 get_class($extension), 217 $key, 218 $is_alias_of)); 219 } 220 } 221 } 222 223 private function newEngineExtensions() { 224 return id(new PhutilClassMapQuery()) 225 ->setAncestorClass('ArcanistConfigurationEngineExtension') 226 ->setUniqueMethod('getExtensionKey') 227 ->setContinueOnFailure(true) 228 ->execute(); 229 } 230 231} 232