• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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