1 // Copyright 2017 The Bazel Authors. All rights reserved. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package com.google.devtools.common.options; 16 17 import com.google.common.base.Preconditions; 18 import com.google.common.collect.ArrayListMultimap; 19 import com.google.common.collect.ImmutableList; 20 import com.google.common.collect.ListMultimap; 21 import com.google.devtools.common.options.OptionsParser.ConstructionException; 22 import java.util.Collection; 23 import java.util.Comparator; 24 import java.util.List; 25 import java.util.Map.Entry; 26 import java.util.stream.Collectors; 27 import javax.annotation.Nullable; 28 29 /** 30 * The value of an option. 31 * 32 * <p>This takes care of tracking the final value as multiple instances of an option are parsed. 33 */ 34 public abstract class OptionValueDescription { 35 36 protected final OptionDefinition optionDefinition; 37 OptionValueDescription(OptionDefinition optionDefinition)38 public OptionValueDescription(OptionDefinition optionDefinition) { 39 this.optionDefinition = optionDefinition; 40 } 41 getOptionDefinition()42 public OptionDefinition getOptionDefinition() { 43 return optionDefinition; 44 } 45 46 /** Returns the current or final value of this option. */ getValue()47 public abstract Object getValue(); 48 49 /** Returns the source(s) of this option, if there were multiple, duplicates are removed. */ getSourceString()50 public abstract String getSourceString(); 51 52 /** 53 * Add an instance of the option to this value. The various types of options are in charge of 54 * making sure that the value is correctly stored, with proper tracking of its priority and 55 * placement amongst other options. 56 * 57 * @return a bundle containing arguments that need to be parsed further. 58 */ addOptionInstance( ParsedOptionDescription parsedOption, List<String> warnings)59 abstract ExpansionBundle addOptionInstance( 60 ParsedOptionDescription parsedOption, List<String> warnings) throws OptionsParsingException; 61 62 /** 63 * Grouping of convenience for the options that expand to other options, to attach an 64 * option-appropriate source string along with the options that need to be parsed. 65 */ 66 public static class ExpansionBundle { 67 List<String> expansionArgs; 68 String sourceOfExpansionArgs; 69 ExpansionBundle(List<String> args, String source)70 public ExpansionBundle(List<String> args, String source) { 71 expansionArgs = args; 72 sourceOfExpansionArgs = source; 73 } 74 } 75 76 /** 77 * Returns the canonical instances of this option - the instances that affect the current value. 78 * 79 * <p>For options that do not have values in their own right, this should be the empty list. In 80 * contrast, the DefaultOptionValue does not have a canonical form at all, since it was never set, 81 * and is null. 82 */ 83 @Nullable getCanonicalInstances()84 public abstract List<ParsedOptionDescription> getCanonicalInstances(); 85 86 /** 87 * For the given option, returns the correct type of OptionValueDescription, to which unparsed 88 * values can be added. 89 * 90 * <p>The categories of option types are non-overlapping, an invariant checked by the 91 * OptionProcessor at compile time. 92 */ createOptionValueDescription( OptionDefinition option, OptionsData optionsData)93 public static OptionValueDescription createOptionValueDescription( 94 OptionDefinition option, OptionsData optionsData) { 95 if (option.isExpansionOption()) { 96 return new ExpansionOptionValueDescription(option, optionsData); 97 } else if (option.allowsMultiple()) { 98 return new RepeatableOptionValueDescription(option); 99 } else if (option.hasImplicitRequirements()) { 100 return new OptionWithImplicitRequirementsValueDescription(option); 101 } else if (option.isWrapperOption()) { 102 return new WrapperOptionValueDescription(option); 103 } else { 104 return new SingleOptionValueDescription(option); 105 } 106 } 107 108 /** 109 * For options that have not been set, this will return a correct OptionValueDescription for the 110 * default value. 111 */ getDefaultOptionValue(OptionDefinition option)112 public static OptionValueDescription getDefaultOptionValue(OptionDefinition option) { 113 return new DefaultOptionValueDescription(option); 114 } 115 116 private static class DefaultOptionValueDescription extends OptionValueDescription { 117 DefaultOptionValueDescription(OptionDefinition optionDefinition)118 private DefaultOptionValueDescription(OptionDefinition optionDefinition) { 119 super(optionDefinition); 120 } 121 122 @Override getValue()123 public Object getValue() { 124 return optionDefinition.getDefaultValue(); 125 } 126 127 @Override getSourceString()128 public String getSourceString() { 129 return null; 130 } 131 132 @Override addOptionInstance(ParsedOptionDescription parsedOption, List<String> warnings)133 ExpansionBundle addOptionInstance(ParsedOptionDescription parsedOption, List<String> warnings) { 134 throw new IllegalStateException( 135 "Cannot add values to the default option value. Create a modifiable " 136 + "OptionValueDescription using createOptionValueDescription() instead."); 137 } 138 139 @Override getCanonicalInstances()140 public ImmutableList<ParsedOptionDescription> getCanonicalInstances() { 141 return null; 142 } 143 } 144 145 /** 146 * The form of a value for a default type of flag, one that does not accumulate multiple values 147 * and has no expansion. 148 */ 149 private static class SingleOptionValueDescription extends OptionValueDescription { 150 private ParsedOptionDescription effectiveOptionInstance; 151 private Object effectiveValue; 152 SingleOptionValueDescription(OptionDefinition optionDefinition)153 private SingleOptionValueDescription(OptionDefinition optionDefinition) { 154 super(optionDefinition); 155 if (optionDefinition.allowsMultiple()) { 156 throw new ConstructionException("Can't have a single value for an allowMultiple option."); 157 } 158 if (optionDefinition.isExpansionOption()) { 159 throw new ConstructionException("Can't have a single value for an expansion option."); 160 } 161 effectiveOptionInstance = null; 162 effectiveValue = null; 163 } 164 165 @Override getValue()166 public Object getValue() { 167 return effectiveValue; 168 } 169 170 @Override getSourceString()171 public String getSourceString() { 172 return effectiveOptionInstance.getSource(); 173 } 174 175 // Warnings should not end with a '.' because the internal reporter adds one automatically. 176 @Override addOptionInstance(ParsedOptionDescription parsedOption, List<String> warnings)177 ExpansionBundle addOptionInstance(ParsedOptionDescription parsedOption, List<String> warnings) 178 throws OptionsParsingException { 179 // This might be the first value, in that case, just store it! 180 if (effectiveOptionInstance == null) { 181 effectiveOptionInstance = parsedOption; 182 effectiveValue = effectiveOptionInstance.getConvertedValue(); 183 return null; 184 } 185 186 // If there was another value, check whether the new one will override it, and if so, 187 // log warnings describing the change. 188 if (parsedOption.getPriority().compareTo(effectiveOptionInstance.getPriority()) >= 0) { 189 // Identify the option that might have led to the current and new value of this option. 190 OptionDefinition implicitDependent = parsedOption.getImplicitDependent(); 191 OptionDefinition expandedFrom = parsedOption.getExpandedFrom(); 192 OptionDefinition optionThatDependsOnEffectiveValue = 193 effectiveOptionInstance.getImplicitDependent(); 194 OptionDefinition optionThatExpandedToEffectiveValue = 195 effectiveOptionInstance.getExpandedFrom(); 196 197 Object newValue = parsedOption.getConvertedValue(); 198 // Output warnings if there is conflicting options set different values in a way that might 199 // not have been obvious to the user, such as through expansions and implicit requirements. 200 if (!effectiveValue.equals(newValue)) { 201 boolean samePriorityCategory = 202 parsedOption 203 .getPriority() 204 .getPriorityCategory() 205 .equals(effectiveOptionInstance.getPriority().getPriorityCategory()); 206 if ((implicitDependent != null) && (optionThatDependsOnEffectiveValue != null)) { 207 if (!implicitDependent.equals(optionThatDependsOnEffectiveValue)) { 208 warnings.add( 209 String.format( 210 "%s is implicitly defined by both %s and %s", 211 optionDefinition, optionThatDependsOnEffectiveValue, implicitDependent)); 212 } 213 } else if ((implicitDependent != null) && samePriorityCategory) { 214 warnings.add( 215 String.format( 216 "%s is implicitly defined by %s; the implicitly set value " 217 + "overrides the previous one", 218 optionDefinition, implicitDependent)); 219 } else if (optionThatDependsOnEffectiveValue != null) { 220 warnings.add( 221 String.format( 222 "A new value for %s overrides a previous implicit setting of that " 223 + "option by %s", 224 optionDefinition, optionThatDependsOnEffectiveValue)); 225 } else if (samePriorityCategory 226 && ((optionThatExpandedToEffectiveValue == null) && (expandedFrom != null))) { 227 // Create a warning if an expansion option overrides an explicit option: 228 warnings.add( 229 String.format( 230 "%s was expanded and now overrides a previous explicitly specified %s with %s", 231 expandedFrom, 232 effectiveOptionInstance.getCommandLineForm(), 233 parsedOption.getCommandLineForm())); 234 } else if ((optionThatExpandedToEffectiveValue != null) && (expandedFrom != null)) { 235 warnings.add( 236 String.format( 237 "%s was expanded to from both %s and %s", 238 optionDefinition, optionThatExpandedToEffectiveValue, expandedFrom)); 239 } 240 } 241 242 // Record the new value: 243 effectiveOptionInstance = parsedOption; 244 effectiveValue = newValue; 245 } 246 return null; 247 } 248 249 @Override getCanonicalInstances()250 public ImmutableList<ParsedOptionDescription> getCanonicalInstances() { 251 // If the current option is an implicit requirement, we don't need to list this value since 252 // the parent implies it. In this case, it is sufficient to not list this value at all. 253 if (effectiveOptionInstance.getImplicitDependent() == null) { 254 return ImmutableList.of(effectiveOptionInstance); 255 } 256 return ImmutableList.of(); 257 } 258 } 259 260 /** The form of a value for an option that accumulates multiple values on the command line. */ 261 private static class RepeatableOptionValueDescription extends OptionValueDescription { 262 ListMultimap<OptionPriority, ParsedOptionDescription> parsedOptions; 263 ListMultimap<OptionPriority, Object> optionValues; 264 RepeatableOptionValueDescription(OptionDefinition optionDefinition)265 private RepeatableOptionValueDescription(OptionDefinition optionDefinition) { 266 super(optionDefinition); 267 if (!optionDefinition.allowsMultiple()) { 268 throw new ConstructionException( 269 "Can't have a repeated value for a non-allowMultiple option."); 270 } 271 parsedOptions = ArrayListMultimap.create(); 272 optionValues = ArrayListMultimap.create(); 273 } 274 275 @Override getSourceString()276 public String getSourceString() { 277 return parsedOptions 278 .asMap() 279 .entrySet() 280 .stream() 281 .sorted(Comparator.comparing(Entry::getKey)) 282 .map(Entry::getValue) 283 .flatMap(Collection::stream) 284 .map(ParsedOptionDescription::getSource) 285 .distinct() 286 .collect(Collectors.joining(", ")); 287 } 288 289 @Override getValue()290 public List<Object> getValue() { 291 // Sort the results by option priority and return them in a new list. The generic type of 292 // the list is not known at runtime, so we can't use it here. 293 return optionValues 294 .asMap() 295 .entrySet() 296 .stream() 297 .sorted(Comparator.comparing(Entry::getKey)) 298 .map(Entry::getValue) 299 .flatMap(Collection::stream) 300 .collect(Collectors.toList()); 301 } 302 303 @Override addOptionInstance(ParsedOptionDescription parsedOption, List<String> warnings)304 ExpansionBundle addOptionInstance(ParsedOptionDescription parsedOption, List<String> warnings) 305 throws OptionsParsingException { 306 // For repeatable options, we allow flags that take both single values and multiple values, 307 // potentially collapsing them down. 308 Object convertedValue = parsedOption.getConvertedValue(); 309 OptionPriority priority = parsedOption.getPriority(); 310 parsedOptions.put(priority, parsedOption); 311 if (convertedValue instanceof List<?>) { 312 optionValues.putAll(priority, (List<?>) convertedValue); 313 } else { 314 optionValues.put(priority, convertedValue); 315 } 316 return null; 317 } 318 319 @Override getCanonicalInstances()320 public ImmutableList<ParsedOptionDescription> getCanonicalInstances() { 321 return parsedOptions 322 .asMap() 323 .entrySet() 324 .stream() 325 .sorted(Comparator.comparing(Entry::getKey)) 326 .map(Entry::getValue) 327 .flatMap(Collection::stream) 328 // Only provide the options that aren't implied elsewhere. 329 .filter(optionDesc -> optionDesc.getImplicitDependent() == null) 330 .collect(ImmutableList.toImmutableList()); 331 } 332 } 333 334 /** 335 * The form of a value for an expansion option, one that does not have its own value but expands 336 * in place to other options. This should be used for both flags with a static expansion defined 337 * in {@link Option#expansion()} and flags with an {@link Option#expansionFunction()}. 338 */ 339 private static class ExpansionOptionValueDescription extends OptionValueDescription { 340 private final List<String> expansion; 341 ExpansionOptionValueDescription( OptionDefinition optionDefinition, OptionsData optionsData)342 private ExpansionOptionValueDescription( 343 OptionDefinition optionDefinition, OptionsData optionsData) { 344 super(optionDefinition); 345 this.expansion = optionsData.getEvaluatedExpansion(optionDefinition); 346 if (!optionDefinition.isExpansionOption()) { 347 throw new ConstructionException( 348 "Options without expansions can't be tracked using ExpansionOptionValueDescription"); 349 } 350 } 351 352 @Override getValue()353 public Object getValue() { 354 return null; 355 } 356 357 @Override getSourceString()358 public String getSourceString() { 359 return null; 360 } 361 362 @Override addOptionInstance(ParsedOptionDescription parsedOption, List<String> warnings)363 ExpansionBundle addOptionInstance(ParsedOptionDescription parsedOption, List<String> warnings) { 364 if (parsedOption.getUnconvertedValue() != null 365 && !parsedOption.getUnconvertedValue().isEmpty()) { 366 warnings.add( 367 String.format( 368 "%s is an expansion option. It does not accept values, and does not change its " 369 + "expansion based on the value provided. Value '%s' will be ignored.", 370 optionDefinition, parsedOption.getUnconvertedValue())); 371 } 372 373 return new ExpansionBundle( 374 expansion, 375 (parsedOption.getSource() == null) 376 ? String.format("expanded from %s", optionDefinition) 377 : String.format( 378 "expanded from %s (source %s)", optionDefinition, parsedOption.getSource())); 379 } 380 381 @Override getCanonicalInstances()382 public ImmutableList<ParsedOptionDescription> getCanonicalInstances() { 383 // The options this expands to are incorporated in their own right - this option does 384 // not have a canonical form. 385 return ImmutableList.of(); 386 } 387 } 388 389 /** The form of a value for a flag with implicit requirements. */ 390 private static class OptionWithImplicitRequirementsValueDescription 391 extends SingleOptionValueDescription { 392 OptionWithImplicitRequirementsValueDescription(OptionDefinition optionDefinition)393 private OptionWithImplicitRequirementsValueDescription(OptionDefinition optionDefinition) { 394 super(optionDefinition); 395 if (!optionDefinition.hasImplicitRequirements()) { 396 throw new ConstructionException( 397 "Options without implicit requirements can't be tracked using " 398 + "OptionWithImplicitRequirementsValueDescription"); 399 } 400 } 401 402 @Override addOptionInstance(ParsedOptionDescription parsedOption, List<String> warnings)403 ExpansionBundle addOptionInstance(ParsedOptionDescription parsedOption, List<String> warnings) 404 throws OptionsParsingException { 405 // This is a valued flag, its value is handled the same way as a normal 406 // SingleOptionValueDescription. (We check at compile time that these flags aren't 407 // "allowMultiple") 408 ExpansionBundle superExpansion = super.addOptionInstance(parsedOption, warnings); 409 Preconditions.checkArgument( 410 superExpansion == null, "SingleOptionValueDescription should not expand to anything."); 411 if (parsedOption.getConvertedValue().equals(optionDefinition.getDefaultValue())) { 412 warnings.add( 413 String.format( 414 "%s sets %s to its default value. Since this option has implicit requirements that " 415 + "are set whenever the option is explicitly provided, regardless of the " 416 + "value, this will behave differently than letting a default be a default. " 417 + "Specifically, this options expands to {%s}.", 418 parsedOption.getCommandLineForm(), 419 optionDefinition, 420 String.join(" ", optionDefinition.getImplicitRequirements()))); 421 } 422 423 // Now deal with the implicit requirements. 424 return new ExpansionBundle( 425 ImmutableList.copyOf(optionDefinition.getImplicitRequirements()), 426 (parsedOption.getSource() == null) 427 ? String.format("implicit requirement of %s", optionDefinition) 428 : String.format( 429 "implicit requirement of %s (source %s)", 430 optionDefinition, parsedOption.getSource())); 431 } 432 } 433 434 /** Form for options that contain other options in the value text to which they expand. */ 435 private static final class WrapperOptionValueDescription extends OptionValueDescription { 436 WrapperOptionValueDescription(OptionDefinition optionDefinition)437 WrapperOptionValueDescription(OptionDefinition optionDefinition) { 438 super(optionDefinition); 439 } 440 441 @Override getValue()442 public Object getValue() { 443 return null; 444 } 445 446 @Override getSourceString()447 public String getSourceString() { 448 return null; 449 } 450 451 @Override addOptionInstance(ParsedOptionDescription parsedOption, List<String> warnings)452 ExpansionBundle addOptionInstance(ParsedOptionDescription parsedOption, List<String> warnings) 453 throws OptionsParsingException { 454 if (!parsedOption.getUnconvertedValue().startsWith("-")) { 455 throw new OptionsParsingException( 456 String.format( 457 "Invalid value format for %s. You may have meant --%s=--%s", 458 optionDefinition, 459 optionDefinition.getOptionName(), 460 parsedOption.getUnconvertedValue())); 461 } 462 return new ExpansionBundle( 463 ImmutableList.of(parsedOption.getUnconvertedValue()), 464 (parsedOption.getSource() == null) 465 ? String.format("unwrapped from %s", optionDefinition) 466 : String.format( 467 "unwrapped from %s (source %s)", optionDefinition, parsedOption.getSource())); 468 } 469 470 @Override getCanonicalInstances()471 public ImmutableList<ParsedOptionDescription> getCanonicalInstances() { 472 // No wrapper options get listed in the canonical form - the options they are wrapping will 473 // be in the right place. 474 return ImmutableList.of(); 475 } 476 } 477 } 478 479 480