|  | // Copyright 2014 The Bazel Authors. All rights reserved. | 
|  | // | 
|  | // Licensed under the Apache License, Version 2.0 (the "License"); | 
|  | // you may not use this file except in compliance with the License. | 
|  | // You may obtain a copy of the License at | 
|  | // | 
|  | //    http://www.apache.org/licenses/LICENSE-2.0 | 
|  | // | 
|  | // Unless required by applicable law or agreed to in writing, software | 
|  | // distributed under the License is distributed on an "AS IS" BASIS, | 
|  | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | 
|  | // See the License for the specific language governing permissions and | 
|  | // limitations under the License. | 
|  |  | 
|  | package com.google.devtools.common.options; | 
|  |  | 
|  | import static com.google.devtools.common.options.OptionPriority.PriorityCategory.INVOCATION_POLICY; | 
|  | import static java.util.Comparator.comparing; | 
|  | import static java.util.stream.Collectors.toCollection; | 
|  |  | 
|  | import com.google.common.base.Joiner; | 
|  | import com.google.common.base.Preconditions; | 
|  | import com.google.common.collect.ImmutableList; | 
|  | import com.google.common.collect.Iterators; | 
|  | import com.google.devtools.common.options.OptionPriority.PriorityCategory; | 
|  | import com.google.devtools.common.options.OptionValueDescription.ExpansionBundle; | 
|  | import com.google.devtools.common.options.OptionsParser.OptionDescription; | 
|  | import java.lang.reflect.Constructor; | 
|  | import java.util.ArrayList; | 
|  | import java.util.Collection; | 
|  | import java.util.HashMap; | 
|  | import java.util.Iterator; | 
|  | import java.util.List; | 
|  | import java.util.Map; | 
|  | import java.util.function.Function; | 
|  | import java.util.stream.Collectors; | 
|  | import java.util.stream.Stream; | 
|  | import javax.annotation.Nullable; | 
|  |  | 
|  | /** | 
|  | * The implementation of the options parser. This is intentionally package private for full | 
|  | * flexibility. Use {@link OptionsParser} or {@link Options} if you're a consumer. | 
|  | */ | 
|  | class OptionsParserImpl { | 
|  |  | 
|  | /** Helper class to create a new instance of {@link OptionsParserImpl}. */ | 
|  | static final class Builder { | 
|  | private OptionsData optionsData; | 
|  | private ArgsPreProcessor argsPreProcessor = args -> args; | 
|  | private final ArrayList<String> skippedPrefixes = new ArrayList<>(); | 
|  | private boolean ignoreInternalOptions = true; | 
|  |  | 
|  | /** Set the {@link OptionsData} to be used in this instance. */ | 
|  | public Builder optionsData(OptionsData optionsData) { | 
|  | this.optionsData = optionsData; | 
|  | return this; | 
|  | } | 
|  |  | 
|  | /** Sets the {@link ArgsPreProcessor} to use during processing. */ | 
|  | public Builder argsPreProcessor(ArgsPreProcessor preProcessor) { | 
|  | this.argsPreProcessor = preProcessor; | 
|  | return this; | 
|  | } | 
|  |  | 
|  | /** Any flags with this prefix will be skipped during processing. */ | 
|  | public Builder skippedPrefix(String skippedPrefix) { | 
|  | this.skippedPrefixes.add(skippedPrefix); | 
|  | return this; | 
|  | } | 
|  |  | 
|  | /** Sets whether the parser should ignore internal-only options. */ | 
|  | public Builder ignoreInternalOptions(boolean ignoreInternalOptions) { | 
|  | this.ignoreInternalOptions = ignoreInternalOptions; | 
|  | return this; | 
|  | } | 
|  |  | 
|  | /** Returns a newly-initialized {@link OptionsParserImpl}. */ | 
|  | public OptionsParserImpl build() { | 
|  | return new OptionsParserImpl( | 
|  | this.optionsData, | 
|  | this.argsPreProcessor, | 
|  | this.skippedPrefixes, | 
|  | this.ignoreInternalOptions); | 
|  | } | 
|  | } | 
|  |  | 
|  | /** Returns a new {@link Builder} with correct defaults applied. */ | 
|  | public static Builder builder() { | 
|  | return new Builder(); | 
|  | } | 
|  |  | 
|  | private final OptionsData optionsData; | 
|  |  | 
|  | /** | 
|  | * We store the results of option parsing in here - since there can only be one value per option | 
|  | * field, this is where the different instances of an option have been combined and the final | 
|  | * value is tracked. It'll look like | 
|  | * | 
|  | * <pre> | 
|  | *   OptionDefinition("--host") -> "www.google.com" | 
|  | *   OptionDefinition("--port") -> 80 | 
|  | * </pre> | 
|  | * | 
|  | * This map is modified by repeated calls to {@link #parse(OptionPriority.PriorityCategory, | 
|  | * Function,List)}. | 
|  | */ | 
|  | private final Map<OptionDefinition, OptionValueDescription> optionValues = new HashMap<>(); | 
|  |  | 
|  | /** | 
|  | * Since parse() expects multiple calls to it with the same {@link PriorityCategory} to be treated | 
|  | * as though the args in the later call have higher priority over the earlier calls, we need to | 
|  | * track the high water mark of option priority at each category. Each call to parse will start at | 
|  | * this level. | 
|  | */ | 
|  | private final Map<PriorityCategory, OptionPriority> nextPriorityPerPriorityCategory = | 
|  | Stream.of(PriorityCategory.values()) | 
|  | .collect(Collectors.toMap(p -> p, OptionPriority::lowestOptionPriorityAtCategory)); | 
|  |  | 
|  | /** | 
|  | * Explicit option tracking, tracking each option as it was provided, after they have been parsed. | 
|  | * | 
|  | * <p>The value is unconverted, still the string as it was read from the input, or partially | 
|  | * altered in cases where the flag was set by non {@code --flag=value} forms; e.g. {@code --nofoo} | 
|  | * becomes {@code --foo=0}. | 
|  | */ | 
|  | private final List<ParsedOptionDescription> parsedOptions = new ArrayList<>(); | 
|  |  | 
|  | private final List<String> warnings = new ArrayList<>(); | 
|  | private final ArgsPreProcessor argsPreProcessor; | 
|  | private final List<String> skippedPrefixes; | 
|  | private final boolean ignoreInternalOptions; | 
|  |  | 
|  | OptionsParserImpl( | 
|  | OptionsData optionsData, | 
|  | ArgsPreProcessor argsPreProcessor, | 
|  | List<String> skippedPrefixes, | 
|  | boolean ignoreInternalOptions) { | 
|  | this.optionsData = optionsData; | 
|  | this.argsPreProcessor = argsPreProcessor; | 
|  | this.skippedPrefixes = skippedPrefixes; | 
|  | this.ignoreInternalOptions = ignoreInternalOptions; | 
|  | } | 
|  |  | 
|  | /** Returns the {@link OptionsData} used in this instance. */ | 
|  | OptionsData getOptionsData() { | 
|  | return optionsData; | 
|  | } | 
|  |  | 
|  | /** Returns a {@link Builder} that is configured the same as this parser. */ | 
|  | Builder toBuilder() { | 
|  | Builder builder = builder().optionsData(optionsData).argsPreProcessor(argsPreProcessor); | 
|  | for (String skippedPrefix : skippedPrefixes) { | 
|  | builder.skippedPrefix(skippedPrefix); | 
|  | } | 
|  | return builder; | 
|  | } | 
|  |  | 
|  | /** Implements {@link OptionsParser#asCompleteListOfParsedOptions()}. */ | 
|  | List<ParsedOptionDescription> asCompleteListOfParsedOptions() { | 
|  | return parsedOptions.stream() | 
|  | // It is vital that this sort is stable so that options on the same priority are not | 
|  | // reordered. | 
|  | .sorted(comparing(ParsedOptionDescription::getPriority)) | 
|  | .collect(toCollection(ArrayList::new)); | 
|  | } | 
|  |  | 
|  | /** Implements {@link OptionsParser#asListOfExplicitOptions()}. */ | 
|  | List<ParsedOptionDescription> asListOfExplicitOptions() { | 
|  | return parsedOptions.stream() | 
|  | .filter(ParsedOptionDescription::isExplicit) | 
|  | // It is vital that this sort is stable so that options on the same priority are not | 
|  | // reordered. | 
|  | .sorted(comparing(ParsedOptionDescription::getPriority)) | 
|  | .collect(toCollection(ArrayList::new)); | 
|  | } | 
|  |  | 
|  | /** Implements {@link OptionsParser#canonicalize}. */ | 
|  | List<String> asCanonicalizedList() { | 
|  | return asCanonicalizedListOfParsedOptions() | 
|  | .stream() | 
|  | .map(ParsedOptionDescription::getDeprecatedCanonicalForm) | 
|  | .collect(ImmutableList.toImmutableList()); | 
|  | } | 
|  |  | 
|  | /** Implements {@link OptionsParser#canonicalize}. */ | 
|  | List<ParsedOptionDescription> asCanonicalizedListOfParsedOptions() { | 
|  | return optionValues.keySet().stream() | 
|  | .map(optionDefinition -> optionValues.get(optionDefinition).getCanonicalInstances()) | 
|  | .flatMap(Collection::stream) | 
|  | // Return the effective (canonical) options in the order they were applied. | 
|  | .sorted(comparing(ParsedOptionDescription::getPriority)) | 
|  | .collect(ImmutableList.toImmutableList()); | 
|  | } | 
|  |  | 
|  | /** Implements {@link OptionsParser#asListOfOptionValues()}. */ | 
|  | List<OptionValueDescription> asListOfEffectiveOptions() { | 
|  | List<OptionValueDescription> result = new ArrayList<>(); | 
|  | for (Map.Entry<String, OptionDefinition> mapEntry : optionsData.getAllOptionDefinitions()) { | 
|  | OptionDefinition optionDefinition = mapEntry.getValue(); | 
|  | OptionValueDescription optionValue = optionValues.get(optionDefinition); | 
|  | if (optionValue == null) { | 
|  | result.add(OptionValueDescription.getDefaultOptionValue(optionDefinition)); | 
|  | } else { | 
|  | result.add(optionValue); | 
|  | } | 
|  | } | 
|  | return result; | 
|  | } | 
|  |  | 
|  | private void maybeAddDeprecationWarning( | 
|  | OptionDefinition optionDefinition, PriorityCategory priority) { | 
|  | // Don't add a warning for deprecated flag set by the invocation policy. | 
|  | if (priority.equals(INVOCATION_POLICY)) { | 
|  | return; | 
|  | } | 
|  | // Continue to support the old behavior for @Deprecated options. | 
|  | String warning = optionDefinition.getDeprecationWarning(); | 
|  | if (!warning.isEmpty() || (optionDefinition.getField().isAnnotationPresent(Deprecated.class))) { | 
|  | addDeprecationWarning(optionDefinition.getOptionName(), warning); | 
|  | } | 
|  | } | 
|  |  | 
|  | private void addDeprecationWarning(String optionName, String warning) { | 
|  | warnings.add( | 
|  | String.format( | 
|  | "Option '%s' is deprecated%s", optionName, (warning.isEmpty() ? "" : ": " + warning))); | 
|  | } | 
|  |  | 
|  |  | 
|  | OptionValueDescription clearValue(OptionDefinition optionDefinition) | 
|  | throws OptionsParsingException { | 
|  | return optionValues.remove(optionDefinition); | 
|  | } | 
|  |  | 
|  | OptionValueDescription getOptionValueDescription(String name) { | 
|  | OptionDefinition optionDefinition = optionsData.getOptionDefinitionFromName(name); | 
|  | if (optionDefinition == null) { | 
|  | throw new IllegalArgumentException("No such option '" + name + "'"); | 
|  | } | 
|  | return optionValues.get(optionDefinition); | 
|  | } | 
|  |  | 
|  | OptionDescription getOptionDescription(String name) throws OptionsParsingException { | 
|  | OptionDefinition optionDefinition = optionsData.getOptionDefinitionFromName(name); | 
|  | if (optionDefinition == null) { | 
|  | return null; | 
|  | } | 
|  | return new OptionDescription(optionDefinition, optionsData); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Implementation of {@link OptionsParser#getExpansionValueDescriptions(OptionDefinition, | 
|  | * OptionInstanceOrigin)} | 
|  | */ | 
|  | ImmutableList<ParsedOptionDescription> getExpansionValueDescriptions( | 
|  | OptionDefinition expansionFlagDef, OptionInstanceOrigin originOfExpansionFlag) | 
|  | throws OptionsParsingException { | 
|  | ImmutableList.Builder<ParsedOptionDescription> builder = ImmutableList.builder(); | 
|  |  | 
|  | // Values needed to correctly track the origin of the expanded options. | 
|  | OptionPriority nextOptionPriority = | 
|  | OptionPriority.getChildPriority(originOfExpansionFlag.getPriority()); | 
|  | String source; | 
|  | ParsedOptionDescription implicitDependent = null; | 
|  | ParsedOptionDescription expandedFrom = null; | 
|  |  | 
|  | ImmutableList<String> options; | 
|  | ParsedOptionDescription expansionFlagParsedDummy = | 
|  | ParsedOptionDescription.newDummyInstance(expansionFlagDef, originOfExpansionFlag); | 
|  | if (expansionFlagDef.hasImplicitRequirements()) { | 
|  | options = ImmutableList.copyOf(expansionFlagDef.getImplicitRequirements()); | 
|  | source = | 
|  | String.format( | 
|  | "implicitly required by %s (source: %s)", | 
|  | expansionFlagDef, originOfExpansionFlag.getSource()); | 
|  | implicitDependent = expansionFlagParsedDummy; | 
|  | } else if (expansionFlagDef.isExpansionOption()) { | 
|  | options = optionsData.getEvaluatedExpansion(expansionFlagDef); | 
|  | source = | 
|  | String.format( | 
|  | "expanded by %s (source: %s)", expansionFlagDef, originOfExpansionFlag.getSource()); | 
|  | expandedFrom = expansionFlagParsedDummy; | 
|  | } else { | 
|  | return ImmutableList.of(); | 
|  | } | 
|  |  | 
|  | Iterator<String> optionsIterator = options.iterator(); | 
|  | while (optionsIterator.hasNext()) { | 
|  | String unparsedFlagExpression = optionsIterator.next(); | 
|  | ParsedOptionDescription parsedOption = | 
|  | identifyOptionAndPossibleArgument( | 
|  | unparsedFlagExpression, | 
|  | optionsIterator, | 
|  | nextOptionPriority, | 
|  | o -> source, | 
|  | implicitDependent, | 
|  | expandedFrom); | 
|  | builder.add(parsedOption); | 
|  | nextOptionPriority = OptionPriority.nextOptionPriority(nextOptionPriority); | 
|  | } | 
|  | return builder.build(); | 
|  | } | 
|  |  | 
|  | boolean containsExplicitOption(String name) { | 
|  | OptionDefinition optionDefinition = optionsData.getOptionDefinitionFromName(name); | 
|  | if (optionDefinition == null) { | 
|  | throw new IllegalArgumentException("No such option '" + name + "'"); | 
|  | } | 
|  | return optionValues.get(optionDefinition) != null; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Parses the args, and returns what it doesn't parse. May be called multiple times, and may be | 
|  | * called recursively. The option's definition dictates how it reacts to multiple settings. By | 
|  | * default, the arg seen last at the highest priority takes precedence, overriding the early | 
|  | * values. Options that accumulate multiple values will track them in priority and appearance | 
|  | * order. | 
|  | */ | 
|  | ResidueAndPriority parse( | 
|  | PriorityCategory priorityCat, | 
|  | Function<OptionDefinition, String> sourceFunction, | 
|  | List<String> args) | 
|  | throws OptionsParsingException { | 
|  | ResidueAndPriority residueAndPriority = | 
|  | parse(nextPriorityPerPriorityCategory.get(priorityCat), sourceFunction, null, null, args); | 
|  | nextPriorityPerPriorityCategory.put(priorityCat, residueAndPriority.nextPriority); | 
|  | return residueAndPriority; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Parses the args, and returns what it doesn't parse. May be called multiple times, and may be | 
|  | * called recursively. Calls may contain intersecting sets of options; in that case, the arg seen | 
|  | * last takes precedence. | 
|  | * | 
|  | * <p>The method treats options that have neither an implicitDependent nor an expandedFrom value | 
|  | * as explicitly set. | 
|  | */ | 
|  | private ResidueAndPriority parse( | 
|  | OptionPriority priority, | 
|  | Function<OptionDefinition, String> sourceFunction, | 
|  | ParsedOptionDescription implicitDependent, | 
|  | ParsedOptionDescription expandedFrom, | 
|  | List<String> args) | 
|  | throws OptionsParsingException { | 
|  | List<String> unparsedArgs = new ArrayList<>(); | 
|  | List<String> unparsedPostDoubleDashArgs = new ArrayList<>(); | 
|  |  | 
|  | Iterator<String> argsIterator = argsPreProcessor.preProcess(args).iterator(); | 
|  | while (argsIterator.hasNext()) { | 
|  | String arg = argsIterator.next(); | 
|  |  | 
|  | if (!arg.startsWith("-")) { | 
|  | unparsedArgs.add(arg); | 
|  | continue; // not an option arg | 
|  | } | 
|  |  | 
|  | if (skippedPrefixes.stream().anyMatch(prefix -> arg.startsWith(prefix))) { | 
|  | unparsedArgs.add(arg); | 
|  | continue; | 
|  | } | 
|  |  | 
|  | if (arg.equals("--")) { // "--" means all remaining args aren't options | 
|  | Iterators.addAll(unparsedPostDoubleDashArgs, argsIterator); | 
|  | break; | 
|  | } | 
|  |  | 
|  | ParsedOptionDescription parsedOption = | 
|  | identifyOptionAndPossibleArgument( | 
|  | arg, argsIterator, priority, sourceFunction, implicitDependent, expandedFrom); | 
|  | handleNewParsedOption(parsedOption); | 
|  | priority = OptionPriority.nextOptionPriority(priority); | 
|  | } | 
|  |  | 
|  | // Go through the final values and make sure they are valid values for their option. Unlike any | 
|  | // checks that happened above, this also checks that flags that were not set have a valid | 
|  | // default value. getValue() will throw if the value is invalid. | 
|  | for (OptionValueDescription valueDescription : asListOfEffectiveOptions()) { | 
|  | valueDescription.getValue(); | 
|  | } | 
|  |  | 
|  | return new ResidueAndPriority(unparsedArgs, unparsedPostDoubleDashArgs, priority); | 
|  | } | 
|  |  | 
|  | /** A class that stores residue and priority information. */ | 
|  | static final class ResidueAndPriority { | 
|  | final List<String> postDoubleDashResidue; | 
|  | final List<String> preDoubleDashResidue; | 
|  | final OptionPriority nextPriority; | 
|  |  | 
|  | ResidueAndPriority( | 
|  | List<String> preDashResidue, List<String> postDashResidue, OptionPriority nextPriority) { | 
|  | this.preDoubleDashResidue = preDashResidue; | 
|  | this.postDoubleDashResidue = postDashResidue; | 
|  | this.nextPriority = nextPriority; | 
|  | } | 
|  |  | 
|  | public List<String> getResidue() { | 
|  | List<String> toReturn = | 
|  | new ArrayList<>(preDoubleDashResidue.size() + postDoubleDashResidue.size()); | 
|  | toReturn.addAll(preDoubleDashResidue); | 
|  | toReturn.addAll(postDoubleDashResidue); | 
|  | return toReturn; | 
|  | } | 
|  | } | 
|  |  | 
|  | /** Implements {@link OptionsParser#parseArgsAsExpansionOfOption} */ | 
|  | ResidueAndPriority parseArgsAsExpansionOfOption( | 
|  | ParsedOptionDescription optionToExpand, | 
|  | Function<OptionDefinition, String> sourceFunction, | 
|  | List<String> args) | 
|  | throws OptionsParsingException { | 
|  | return parse( | 
|  | OptionPriority.getChildPriority(optionToExpand.getPriority()), | 
|  | sourceFunction, | 
|  | null, | 
|  | optionToExpand, | 
|  | args); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Implementation of {@link OptionsParser#addOptionValueAtSpecificPriority(OptionInstanceOrigin, | 
|  | * OptionDefinition, String)} | 
|  | */ | 
|  | void addOptionValueAtSpecificPriority( | 
|  | OptionInstanceOrigin origin, OptionDefinition option, String unconvertedValue) | 
|  | throws OptionsParsingException { | 
|  | Preconditions.checkNotNull(option); | 
|  | Preconditions.checkNotNull( | 
|  | unconvertedValue, | 
|  | "Cannot set %s to a null value. Pass \"\" if an empty value is required.", | 
|  | option); | 
|  | Preconditions.checkNotNull( | 
|  | origin, | 
|  | "Cannot assign value \'%s\' to %s without a clear origin for this value.", | 
|  | unconvertedValue, | 
|  | option); | 
|  | PriorityCategory priorityCategory = origin.getPriority().getPriorityCategory(); | 
|  | boolean isNotDefault = priorityCategory != OptionPriority.PriorityCategory.DEFAULT; | 
|  | Preconditions.checkArgument( | 
|  | isNotDefault, | 
|  | "Attempt to assign value \'%s\' to %s at priority %s failed. Cannot set options at " | 
|  | + "default priority - by definition, that means the option is unset.", | 
|  | unconvertedValue, | 
|  | option, | 
|  | priorityCategory); | 
|  |  | 
|  | handleNewParsedOption( | 
|  | ParsedOptionDescription.newParsedOptionDescription( | 
|  | option, | 
|  | String.format("--%s=%s", option.getOptionName(), unconvertedValue), | 
|  | unconvertedValue, | 
|  | origin)); | 
|  | } | 
|  |  | 
|  | /** Takes care of tracking the parsed option's value in relation to other options. */ | 
|  | private void handleNewParsedOption(ParsedOptionDescription parsedOption) | 
|  | throws OptionsParsingException { | 
|  | OptionDefinition optionDefinition = parsedOption.getOptionDefinition(); | 
|  | // All options can be deprecated; check and warn before doing any option-type specific work. | 
|  | maybeAddDeprecationWarning(optionDefinition, parsedOption.getPriority().getPriorityCategory()); | 
|  | // Track the value, before any remaining option-type specific work that is done outside of | 
|  | // the OptionValueDescription. | 
|  | OptionValueDescription entry = | 
|  | optionValues.computeIfAbsent( | 
|  | optionDefinition, | 
|  | def -> OptionValueDescription.createOptionValueDescription(def, optionsData)); | 
|  | ExpansionBundle expansionBundle = entry.addOptionInstance(parsedOption, warnings); | 
|  | @Nullable String unconvertedValue = parsedOption.getUnconvertedValue(); | 
|  |  | 
|  | // There are 3 types of flags that expand to other flag values. Expansion flags are the | 
|  | // accepted way to do this, but implicit requirements also do this. We rely on the | 
|  | // OptionProcessor compile-time check's guarantee that no option sets | 
|  | // both expansion behaviors. (In Bazel, --config is another such flag, but that expansion | 
|  | // is not controlled within the options parser, so we ignore it here) | 
|  |  | 
|  | // As much as possible, we want the behaviors of these different types of flags to be | 
|  | // identical, as this minimizes the number of edge cases, but we do not yet track these values | 
|  | // in the same way. | 
|  | if (parsedOption.getImplicitDependent() == null) { | 
|  | // Log explicit options and expanded options in the order they are parsed (can be sorted | 
|  | // later). This information is needed to correctly canonicalize flags. | 
|  | parsedOptions.add(parsedOption); | 
|  | } | 
|  |  | 
|  | if (expansionBundle != null) { | 
|  | ResidueAndPriority residueAndPriority = | 
|  | parse( | 
|  | OptionPriority.getChildPriority(parsedOption.getPriority()), | 
|  | o -> expansionBundle.sourceOfExpansionArgs, | 
|  | optionDefinition.hasImplicitRequirements() ? parsedOption : null, | 
|  | optionDefinition.isExpansionOption() ? parsedOption : null, | 
|  | expansionBundle.expansionArgs); | 
|  | if (!residueAndPriority.getResidue().isEmpty()) { | 
|  |  | 
|  | // Throw an assertion here, because this indicates an error in the definition of this | 
|  | // option's expansion or requirements, not with the input as provided by the user. | 
|  | throw new AssertionError( | 
|  | "Unparsed options remain after processing " | 
|  | + unconvertedValue | 
|  | + ": " | 
|  | + Joiner.on(' ').join(residueAndPriority.getResidue())); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | private ParsedOptionDescription identifyOptionAndPossibleArgument( | 
|  | String arg, | 
|  | Iterator<String> nextArgs, | 
|  | OptionPriority priority, | 
|  | Function<OptionDefinition, String> sourceFunction, | 
|  | ParsedOptionDescription implicitDependent, | 
|  | ParsedOptionDescription expandedFrom) | 
|  | throws OptionsParsingException { | 
|  |  | 
|  | // Store the way this option was parsed on the command line. | 
|  | StringBuilder commandLineForm = new StringBuilder(); | 
|  | commandLineForm.append(arg); | 
|  | String unconvertedValue = null; | 
|  | OptionDefinition optionDefinition; | 
|  | boolean booleanValue = true; | 
|  |  | 
|  | if (arg.length() == 2) { // -l  (may be nullary or unary) | 
|  | optionDefinition = optionsData.getFieldForAbbrev(arg.charAt(1)); | 
|  | booleanValue = true; | 
|  |  | 
|  | } else if (arg.length() == 3 && arg.charAt(2) == '-') { // -l-  (boolean) | 
|  | optionDefinition = optionsData.getFieldForAbbrev(arg.charAt(1)); | 
|  | booleanValue = false; | 
|  |  | 
|  | } else if (arg.startsWith("--")) { // --long_option | 
|  |  | 
|  | int equalsAt = arg.indexOf('='); | 
|  | int nameStartsAt = arg.startsWith("--") ? 2 : 1; | 
|  | String name = | 
|  | equalsAt == -1 ? arg.substring(nameStartsAt) : arg.substring(nameStartsAt, equalsAt); | 
|  | if (name.trim().isEmpty()) { | 
|  | throw new OptionsParsingException("Invalid options syntax: " + arg, arg); | 
|  | } | 
|  | unconvertedValue = equalsAt == -1 ? null : arg.substring(equalsAt + 1); | 
|  | optionDefinition = optionsData.getOptionDefinitionFromName(name); | 
|  |  | 
|  | // Look for a "no"-prefixed option name: "no<optionName>". | 
|  | if (optionDefinition == null && name.startsWith("no")) { | 
|  | name = name.substring(2); | 
|  | optionDefinition = optionsData.getOptionDefinitionFromName(name); | 
|  | booleanValue = false; | 
|  | if (optionDefinition != null) { | 
|  | // TODO(bazel-team): Add tests for these cases. | 
|  | if (!optionDefinition.usesBooleanValueSyntax()) { | 
|  | throw new OptionsParsingException( | 
|  | "Illegal use of 'no' prefix on non-boolean option: " + arg, arg); | 
|  | } | 
|  | if (unconvertedValue != null) { | 
|  | throw new OptionsParsingException( | 
|  | "Unexpected value after boolean option: " + arg, arg); | 
|  | } | 
|  | // "no<optionname>" signifies a boolean option w/ false value | 
|  | unconvertedValue = "0"; | 
|  | } | 
|  | } | 
|  | } else { | 
|  | throw new OptionsParsingException("Invalid options syntax: " + arg, arg); | 
|  | } | 
|  |  | 
|  | if (optionDefinition == null || shouldIgnoreOption(optionDefinition)) { | 
|  | // Do not recognize internal options, which are treated as if they did not exist. | 
|  | throw new OptionsParsingException("Unrecognized option: " + arg, arg); | 
|  | } | 
|  |  | 
|  | if (unconvertedValue == null) { | 
|  | // Special-case boolean to supply value based on presence of "no" prefix. | 
|  | if (optionDefinition.usesBooleanValueSyntax()) { | 
|  | unconvertedValue = booleanValue ? "1" : "0"; | 
|  | } else if (optionDefinition.getType().equals(Void.class)) { | 
|  | // This is expected, Void type options have no args. | 
|  | } else if (nextArgs.hasNext()) { | 
|  | // "--flag value" form | 
|  | unconvertedValue = nextArgs.next(); | 
|  | commandLineForm.append(" ").append(unconvertedValue); | 
|  | } else { | 
|  | throw new OptionsParsingException("Expected value after " + arg); | 
|  | } | 
|  | } | 
|  |  | 
|  | return ParsedOptionDescription.newParsedOptionDescription( | 
|  | optionDefinition, | 
|  | commandLineForm.toString(), | 
|  | unconvertedValue, | 
|  | new OptionInstanceOrigin( | 
|  | priority, sourceFunction.apply(optionDefinition), implicitDependent, expandedFrom)); | 
|  | } | 
|  |  | 
|  | private boolean shouldIgnoreOption(OptionDefinition optionDefinition) { | 
|  | return ignoreInternalOptions | 
|  | && ImmutableList.copyOf(optionDefinition.getOptionMetadataTags()) | 
|  | .contains(OptionMetadataTag.INTERNAL); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * Gets the result of parsing the options. | 
|  | */ | 
|  | <O extends OptionsBase> O getParsedOptions(Class<O> optionsClass) { | 
|  | // Create the instance: | 
|  | O optionsInstance; | 
|  | try { | 
|  | Constructor<O> constructor = optionsData.getConstructor(optionsClass); | 
|  | if (constructor == null) { | 
|  | return null; | 
|  | } | 
|  | optionsInstance = constructor.newInstance(); | 
|  | } catch (ReflectiveOperationException e) { | 
|  | throw new IllegalStateException("Error while instantiating options class", e); | 
|  | } | 
|  |  | 
|  | // Set the fields | 
|  | for (OptionDefinition optionDefinition : | 
|  | OptionsData.getAllOptionDefinitionsForClass(optionsClass)) { | 
|  | Object value; | 
|  | OptionValueDescription optionValue = optionValues.get(optionDefinition); | 
|  | if (optionValue == null) { | 
|  | value = optionDefinition.getDefaultValue(); | 
|  | } else { | 
|  | value = optionValue.getValue(); | 
|  | } | 
|  | try { | 
|  | optionDefinition.getField().set(optionsInstance, value); | 
|  | } catch (IllegalArgumentException e) { | 
|  | // May happen when a boolean option got a string value. Just ignore this error without | 
|  | // updating the field. Fixes https://github.com/bazelbuild/bazel/issues/7847 | 
|  | } catch (IllegalAccessException e) { | 
|  | throw new IllegalStateException( | 
|  | "Could not set the field due to access issues. This is impossible, as the " | 
|  | + "OptionProcessor checks that all options are non-final public fields.", | 
|  | e); | 
|  | } | 
|  | } | 
|  | return optionsInstance; | 
|  | } | 
|  |  | 
|  | List<String> getWarnings() { | 
|  | return ImmutableList.copyOf(warnings); | 
|  | } | 
|  | } |