blob: 886793c3b02e60c6dc68905bbe522345ab19af15 [file] [log] [blame]
Damien Martin-Guillerezebb00392015-09-29 11:55:59 +00001// Copyright 2015 The Bazel Authors. All rights reserved.
Alex Humesky2f3f4cf2015-09-29 01:42:00 +00002//
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.
ccalvarinf39dc6f2017-06-09 11:51:45 -040014package com.google.devtools.common.options;
Alex Humesky2f3f4cf2015-09-29 01:42:00 +000015
Googler4442bb92021-10-15 09:15:28 -070016import static java.util.concurrent.TimeUnit.MINUTES;
janakrc9560212020-05-27 15:07:36 -070017import static java.util.stream.Collectors.joining;
18
Alex Humesky2f3f4cf2015-09-29 01:42:00 +000019import com.google.common.base.Verify;
ccalvarinae1d0de2017-04-15 05:19:09 +020020import com.google.common.collect.ArrayListMultimap;
ccalvarin706bafe2017-03-30 17:34:04 +000021import com.google.common.collect.ImmutableList;
Janak Ramakrishnane9cd0f32016-04-29 22:46:16 +000022import com.google.common.collect.ImmutableSet;
ccalvarinae1d0de2017-04-15 05:19:09 +020023import com.google.common.collect.Multimap;
janakrc9560212020-05-27 15:07:36 -070024import com.google.common.flogger.GoogleLogger;
25import com.google.common.flogger.LazyArgs;
Alex Humesky2f3f4cf2015-09-29 01:42:00 +000026import com.google.devtools.build.lib.runtime.proto.InvocationPolicyOuterClass.AllowValues;
27import com.google.devtools.build.lib.runtime.proto.InvocationPolicyOuterClass.DisallowValues;
28import com.google.devtools.build.lib.runtime.proto.InvocationPolicyOuterClass.FlagPolicy;
ccalvarinae1d0de2017-04-15 05:19:09 +020029import com.google.devtools.build.lib.runtime.proto.InvocationPolicyOuterClass.FlagPolicy.OperationCase;
Alex Humesky2f3f4cf2015-09-29 01:42:00 +000030import com.google.devtools.build.lib.runtime.proto.InvocationPolicyOuterClass.InvocationPolicy;
31import com.google.devtools.build.lib.runtime.proto.InvocationPolicyOuterClass.SetValue;
ajurkowskiecab2972021-06-17 14:26:34 -070032import com.google.devtools.build.lib.runtime.proto.InvocationPolicyOuterClass.SetValue.Behavior;
ccalvarin706bafe2017-03-30 17:34:04 +000033import com.google.devtools.build.lib.runtime.proto.InvocationPolicyOuterClass.UseDefault;
ccalvarin7cd9e882017-10-16 22:18:32 +020034import com.google.devtools.common.options.OptionPriority.PriorityCategory;
Alex Humesky2f3f4cf2015-09-29 01:42:00 +000035import com.google.devtools.common.options.OptionsParser.OptionDescription;
ccalvarin706bafe2017-03-30 17:34:04 +000036import java.util.ArrayList;
ccalvarinefc9b632017-05-03 19:05:22 +020037import java.util.Collections;
38import java.util.HashMap;
Jonathan Bluett-Duncan0df3ddbd2017-08-09 11:13:54 +020039import java.util.HashSet;
Alex Humesky2f3f4cf2015-09-29 01:42:00 +000040import java.util.List;
ccalvarinefc9b632017-05-03 19:05:22 +020041import java.util.Map;
Alex Humesky2f3f4cf2015-09-29 01:42:00 +000042import java.util.Set;
Janak Ramakrishnanb40cbaa2016-11-18 00:20:45 +000043import java.util.logging.Level;
Alex Humesky2f3f4cf2015-09-29 01:42:00 +000044import javax.annotation.Nullable;
45
46/**
ccalvarin334d2f12017-10-05 16:39:42 +020047 * Enforces the {@link FlagPolicy}s (from an {@link InvocationPolicy} proto) on an {@link
48 * OptionsParser} by validating and changing the flag values in the given {@link OptionsParser}.
Alex Humesky2f3f4cf2015-09-29 01:42:00 +000049 *
50 * <p>"Flag" and "Option" are used interchangeably in this file.
51 */
52public final class InvocationPolicyEnforcer {
janakrc9560212020-05-27 15:07:36 -070053 private static final GoogleLogger logger = GoogleLogger.forEnclosingClass();
Alex Humeskyc0d27692016-02-03 00:52:04 +000054
ccalvarin5fe8e662017-09-14 15:56:43 +020055 private static final String INVOCATION_POLICY_SOURCE = "Invocation policy";
Janak Ramakrishnanb92c0972016-03-23 16:47:13 +000056 @Nullable private final InvocationPolicy invocationPolicy;
ccalvarin334d2f12017-10-05 16:39:42 +020057 private final Level loglevel;
Alex Humesky2f3f4cf2015-09-29 01:42:00 +000058
Alex Humeskyc0d27692016-02-03 00:52:04 +000059 /**
60 * Creates an InvocationPolicyEnforcer that enforces the given policy.
61 *
62 * @param invocationPolicy the policy to enforce. A null policy means this enforcer will do
63 * nothing in calls to enforce().
64 */
Alex Humesky2f3f4cf2015-09-29 01:42:00 +000065 public InvocationPolicyEnforcer(@Nullable InvocationPolicy invocationPolicy) {
ccalvarin334d2f12017-10-05 16:39:42 +020066 this(invocationPolicy, Level.FINE);
67 }
68
69 /**
70 * Creates an InvocationPolicyEnforcer that enforces the given policy.
71 *
72 * @param invocationPolicy the policy to enforce. A null policy means this enforcer will do
73 * nothing in calls to enforce().
74 * @param loglevel the level at which to log informational statements. Warnings and errors will
75 * still be logged at the appropriate level.
76 */
77 public InvocationPolicyEnforcer(@Nullable InvocationPolicy invocationPolicy, Level loglevel) {
Alex Humesky2f3f4cf2015-09-29 01:42:00 +000078 this.invocationPolicy = invocationPolicy;
ccalvarin334d2f12017-10-05 16:39:42 +020079 this.loglevel = loglevel;
Alex Humesky2f3f4cf2015-09-29 01:42:00 +000080 }
81
ccalvarinca744822017-10-10 13:50:15 +020082 private static final class FlagPolicyWithContext {
83 private final FlagPolicy policy;
84 private final OptionDescription description;
ccalvarin7cd9e882017-10-16 22:18:32 +020085 private final OptionInstanceOrigin origin;
ccalvarinca744822017-10-10 13:50:15 +020086
ccalvarin7cd9e882017-10-16 22:18:32 +020087 public FlagPolicyWithContext(
88 FlagPolicy policy, OptionDescription description, OptionInstanceOrigin origin) {
ccalvarinca744822017-10-10 13:50:15 +020089 this.policy = policy;
90 this.description = description;
ccalvarin7cd9e882017-10-16 22:18:32 +020091 this.origin = origin;
ccalvarinca744822017-10-10 13:50:15 +020092 }
93 }
94
Ulf Adams015aad92016-07-13 16:49:40 +000095 public InvocationPolicy getInvocationPolicy() {
96 return invocationPolicy;
97 }
98
Alex Humesky2f3f4cf2015-09-29 01:42:00 +000099 /**
Luis Fernando Pino Duqueb1b28b62016-02-25 14:25:19 +0000100 * Applies this OptionsPolicyEnforcer's policy to the given OptionsParser for all blaze commands.
101 *
102 * @param parser The OptionsParser to enforce policy on.
103 * @throws OptionsParsingException if any flag policy is invalid.
104 */
105 public void enforce(OptionsParser parser) throws OptionsParsingException {
Janak Ramakrishnane9cd0f32016-04-29 22:46:16 +0000106 enforce(parser, null);
Luis Fernando Pino Duqueb1b28b62016-02-25 14:25:19 +0000107 }
108
109 /**
Alex Humesky2f3f4cf2015-09-29 01:42:00 +0000110 * Applies this OptionsPolicyEnforcer's policy to the given OptionsParser.
111 *
112 * @param parser The OptionsParser to enforce policy on.
Alex Humeskyc0d27692016-02-03 00:52:04 +0000113 * @param command The current blaze command, for flag policies that apply to only specific
Janak Ramakrishnane9cd0f32016-04-29 22:46:16 +0000114 * commands. Such policies will be enforced only if they contain this command or a command
115 * they inherit from
Alex Humeskyc0d27692016-02-03 00:52:04 +0000116 * @throws OptionsParsingException if any flag policy is invalid.
Alex Humesky2f3f4cf2015-09-29 01:42:00 +0000117 */
Janak Ramakrishnane9cd0f32016-04-29 22:46:16 +0000118 public void enforce(OptionsParser parser, @Nullable String command)
119 throws OptionsParsingException {
Luis Fernando Pino Duque5816b3b2016-04-11 15:33:26 +0000120 if (invocationPolicy == null || invocationPolicy.getFlagPoliciesCount() == 0) {
Alex Humeskyc0d27692016-02-03 00:52:04 +0000121 return;
Alex Humesky2f3f4cf2015-09-29 01:42:00 +0000122 }
123
Googler4442bb92021-10-15 09:15:28 -0700124 // TODO(b/186167747): Remove the warning once we migrate to the new enum.
125 invocationPolicy.getFlagPoliciesList().stream()
126 .filter(p -> p.hasSetValue() && p.getSetValue().getBehavior() == Behavior.UNDEFINED)
127 .findFirst()
128 .ifPresent(
129 policy ->
130 logger.atWarning().atMostEvery(5, MINUTES).log(
131 "Invocation policy has missing/undefined behavior: %s", policy));
132
ccalvarinefc9b632017-05-03 19:05:22 +0200133 // The effective policy returned is expanded, filtered for applicable commands, and cleaned of
134 // redundancies and conflicts.
ccalvarinca744822017-10-10 13:50:15 +0200135 List<FlagPolicyWithContext> effectivePolicies =
ccalvarin334d2f12017-10-05 16:39:42 +0200136 getEffectivePolicies(invocationPolicy, parser, command, loglevel);
ccalvarinefc9b632017-05-03 19:05:22 +0200137
ccalvarinca744822017-10-10 13:50:15 +0200138 for (FlagPolicyWithContext flagPolicy : effectivePolicies) {
139 String flagName = flagPolicy.policy.getFlagName();
Alex Humesky2f3f4cf2015-09-29 01:42:00 +0000140
Alex Humesky2f3f4cf2015-09-29 01:42:00 +0000141 OptionValueDescription valueDescription;
142 try {
143 valueDescription = parser.getOptionValueDescription(flagName);
144 } catch (IllegalArgumentException e) {
145 // This flag doesn't exist. We are deliberately lenient if the flag policy has a flag
146 // we don't know about. This is for better future proofing so that as new flags are added,
Luis Fernando Pino Duqueb1b28b62016-02-25 14:25:19 +0000147 // new policies can use the new flags without worrying about older versions of Bazel.
janakrc9560212020-05-27 15:07:36 -0700148 logger.at(loglevel).log(
149 "Flag '%s' specified by invocation policy does not exist", flagName);
Alex Humesky2f3f4cf2015-09-29 01:42:00 +0000150 continue;
151 }
152
ccalvarin5fe8e662017-09-14 15:56:43 +0200153 // getOptionDescription() will return null if the option does not exist, however
Alex Humesky2f3f4cf2015-09-29 01:42:00 +0000154 // getOptionValueDescription() above would have thrown an IllegalArgumentException if that
155 // were the case.
ccalvarinca744822017-10-10 13:50:15 +0200156 Verify.verifyNotNull(flagPolicy.description);
Alex Humesky2f3f4cf2015-09-29 01:42:00 +0000157
ccalvarinca744822017-10-10 13:50:15 +0200158 switch (flagPolicy.policy.getOperationCase()) {
Alex Humesky2f3f4cf2015-09-29 01:42:00 +0000159 case SET_VALUE:
ccalvarinca744822017-10-10 13:50:15 +0200160 applySetValueOperation(parser, flagPolicy, valueDescription, loglevel);
Alex Humesky2f3f4cf2015-09-29 01:42:00 +0000161 break;
162
163 case USE_DEFAULT:
ccalvarin334d2f12017-10-05 16:39:42 +0200164 applyUseDefaultOperation(
ccalvarinca744822017-10-10 13:50:15 +0200165 parser, "UseDefault", flagPolicy.description.getOptionDefinition(), loglevel);
Alex Humesky2f3f4cf2015-09-29 01:42:00 +0000166 break;
167
168 case ALLOW_VALUES:
ccalvarinca744822017-10-10 13:50:15 +0200169 AllowValues allowValues = flagPolicy.policy.getAllowValues();
ccalvarin334d2f12017-10-05 16:39:42 +0200170 FilterValueOperation.AllowValueOperation allowValueOperation =
171 new FilterValueOperation.AllowValueOperation(loglevel);
172 allowValueOperation.apply(
Alex Humeskyc0d27692016-02-03 00:52:04 +0000173 parser,
ccalvarin7cd9e882017-10-16 22:18:32 +0200174 flagPolicy.origin,
Alex Humeskyc0d27692016-02-03 00:52:04 +0000175 allowValues.getAllowedValuesList(),
Alex Humeskyb1f55c82016-10-25 01:06:39 +0000176 allowValues.hasNewValue() ? allowValues.getNewValue() : null,
177 allowValues.hasUseDefault(),
Alex Humeskyc0d27692016-02-03 00:52:04 +0000178 valueDescription,
ccalvarinca744822017-10-10 13:50:15 +0200179 flagPolicy.description);
Alex Humesky2f3f4cf2015-09-29 01:42:00 +0000180 break;
181
182 case DISALLOW_VALUES:
ccalvarinca744822017-10-10 13:50:15 +0200183 DisallowValues disallowValues = flagPolicy.policy.getDisallowValues();
ccalvarin334d2f12017-10-05 16:39:42 +0200184 FilterValueOperation.DisallowValueOperation disallowValueOperation =
185 new FilterValueOperation.DisallowValueOperation(loglevel);
186 disallowValueOperation.apply(
Alex Humeskyc0d27692016-02-03 00:52:04 +0000187 parser,
ccalvarin7cd9e882017-10-16 22:18:32 +0200188 flagPolicy.origin,
Alex Humeskyc0d27692016-02-03 00:52:04 +0000189 disallowValues.getDisallowedValuesList(),
Alex Humeskyb1f55c82016-10-25 01:06:39 +0000190 disallowValues.hasNewValue() ? disallowValues.getNewValue() : null,
191 disallowValues.hasUseDefault(),
Alex Humeskyc0d27692016-02-03 00:52:04 +0000192 valueDescription,
ccalvarinca744822017-10-10 13:50:15 +0200193 flagPolicy.description);
Alex Humesky2f3f4cf2015-09-29 01:42:00 +0000194 break;
195
196 case OPERATION_NOT_SET:
ccalvarin706bafe2017-03-30 17:34:04 +0000197 throw new PolicyOperationNotSetException(flagName);
Alex Humesky2f3f4cf2015-09-29 01:42:00 +0000198
199 default:
janakrc9560212020-05-27 15:07:36 -0700200 logger.atWarning().log(
201 "Unknown operation '%s' from invocation policy for flag '%s'",
202 flagPolicy.policy.getOperationCase(), flagName);
Alex Humesky2f3f4cf2015-09-29 01:42:00 +0000203 break;
204 }
205 }
206 }
207
ccalvarin706bafe2017-03-30 17:34:04 +0000208 private static class PolicyOperationNotSetException extends OptionsParsingException {
209 PolicyOperationNotSetException(String flagName) {
210 super(String.format("Flag policy for flag '%s' does not " + "have an operation", flagName));
211 }
212 }
213
ccalvarinefc9b632017-05-03 19:05:22 +0200214 private static boolean policyApplies(FlagPolicy policy, ImmutableSet<String> applicableCommands) {
215 // Skip the flag policy if it doesn't apply to this command. If the commands list is empty,
216 // then the policy applies to all commands.
217 if (policy.getCommandsList().isEmpty() || applicableCommands.isEmpty()) {
218 return true;
219 }
220
221 return !Collections.disjoint(policy.getCommandsList(), applicableCommands);
222 }
223
ccalvarinca744822017-10-10 13:50:15 +0200224 /** Returns the expanded and filtered policy that would be enforced for the given command. */
225 public static InvocationPolicy getEffectiveInvocationPolicy(
226 InvocationPolicy invocationPolicy, OptionsParser parser, String command, Level loglevel)
227 throws OptionsParsingException {
228 ImmutableList<FlagPolicyWithContext> effectivePolicies =
229 getEffectivePolicies(invocationPolicy, parser, command, loglevel);
230
231 InvocationPolicy.Builder builder = InvocationPolicy.newBuilder();
232 for (FlagPolicyWithContext policyWithContext : effectivePolicies) {
233 builder.addFlagPolicies(policyWithContext.policy);
234 }
235 return builder.build();
236 }
237
ccalvarin706bafe2017-03-30 17:34:04 +0000238 /**
239 * Takes the provided policy and processes it to the form that can be used on the user options.
240 *
241 * <p>Expands any policies on expansion flags.
242 */
ccalvarinca744822017-10-10 13:50:15 +0200243 private static ImmutableList<FlagPolicyWithContext> getEffectivePolicies(
ccalvarin334d2f12017-10-05 16:39:42 +0200244 InvocationPolicy invocationPolicy, OptionsParser parser, String command, Level loglevel)
ccalvarinefc9b632017-05-03 19:05:22 +0200245 throws OptionsParsingException {
ccalvarin706bafe2017-03-30 17:34:04 +0000246 if (invocationPolicy == null) {
247 return ImmutableList.of();
248 }
249
ccalvarinefc9b632017-05-03 19:05:22 +0200250 ImmutableSet<String> commandAndParentCommands =
251 command == null
Jonathan Bluett-Duncan0df3ddbd2017-08-09 11:13:54 +0200252 ? ImmutableSet.of()
ccalvarinefc9b632017-05-03 19:05:22 +0200253 : CommandNameCache.CommandNameCacheInstance.INSTANCE.get(command);
254
ccalvarin706bafe2017-03-30 17:34:04 +0000255 // Expand all policies to transfer policies on expansion flags to policies on the child flags.
ccalvarinca744822017-10-10 13:50:15 +0200256 List<FlagPolicyWithContext> expandedPolicies = new ArrayList<>();
ccalvarin7cd9e882017-10-16 22:18:32 +0200257 OptionPriority nextPriority =
258 OptionPriority.lowestOptionPriorityAtCategory(PriorityCategory.INVOCATION_POLICY);
ccalvarin706bafe2017-03-30 17:34:04 +0000259 for (FlagPolicy policy : invocationPolicy.getFlagPoliciesList()) {
ccalvarin9c8c7752018-04-17 07:48:38 -0700260 // Explicitly disallow --config in invocation policy.
261 if (policy.getFlagName().equals("config")) {
262 throw new OptionsParsingException(
263 "Invocation policy is applied after --config expansion, changing config values now "
264 + "would have no effect and is disallowed to prevent confusion. Please remove the "
265 + "following policy : "
266 + policy);
267 }
268
ccalvarin7cd9e882017-10-16 22:18:32 +0200269 // These policies are high-level, before expansion, and so are not the implicitDependents or
270 // expansions of any other flag, other than in an obtuse sense from --invocation_policy.
271 OptionPriority currentPriority = nextPriority;
272 OptionInstanceOrigin origin =
273 new OptionInstanceOrigin(currentPriority, INVOCATION_POLICY_SOURCE, null, null);
274 nextPriority = OptionPriority.nextOptionPriority(currentPriority);
ccalvarinefc9b632017-05-03 19:05:22 +0200275 if (!policyApplies(policy, commandAndParentCommands)) {
276 // Only keep and expand policies that are applicable to the current command.
277 continue;
278 }
ccalvarin7cd9e882017-10-16 22:18:32 +0200279
ccalvarin34a9fea2017-10-17 23:27:19 +0200280 OptionDescription optionDescription = parser.getOptionDescription(policy.getFlagName());
ccalvarinca744822017-10-10 13:50:15 +0200281 if (optionDescription == null) {
282 // InvocationPolicy ignores policy on non-existing flags by design, for version
283 // compatibility.
janakrc9560212020-05-27 15:07:36 -0700284 logger.at(loglevel).log(
285 "Flag '%s' specified by invocation policy does not exist, and will be ignored",
286 policy.getFlagName());
ccalvarinca744822017-10-10 13:50:15 +0200287 continue;
288 }
289 FlagPolicyWithContext policyWithContext =
ccalvarin7cd9e882017-10-16 22:18:32 +0200290 new FlagPolicyWithContext(policy, optionDescription, origin);
ccalvarin34a9fea2017-10-17 23:27:19 +0200291 List<FlagPolicyWithContext> policies = expandPolicy(policyWithContext, parser, loglevel);
ccalvarin706bafe2017-03-30 17:34:04 +0000292 expandedPolicies.addAll(policies);
293 }
294
ccalvarinefc9b632017-05-03 19:05:22 +0200295 // Only keep that last policy for each flag.
ccalvarinca744822017-10-10 13:50:15 +0200296 Map<String, FlagPolicyWithContext> effectivePolicy = new HashMap<>();
297 for (FlagPolicyWithContext expandedPolicy : expandedPolicies) {
298 String flagName = expandedPolicy.policy.getFlagName();
ccalvarinefc9b632017-05-03 19:05:22 +0200299 effectivePolicy.put(flagName, expandedPolicy);
300 }
301
Googler7c7255e2017-06-27 20:05:20 +0200302 return ImmutableList.copyOf(effectivePolicy.values());
303 }
304
305 private static void throwAllowValuesOnExpansionFlagException(String flagName)
306 throws OptionsParsingException {
307 throw new OptionsParsingException(
308 String.format("Allow_Values on expansion flags like %s is not allowed.", flagName));
309 }
310
311 private static void throwDisallowValuesOnExpansionFlagException(String flagName)
312 throws OptionsParsingException {
313 throw new OptionsParsingException(
314 String.format("Disallow_Values on expansion flags like %s is not allowed.", flagName));
315 }
316
ccalvarin706bafe2017-03-30 17:34:04 +0000317 /**
318 * Expand a single policy. If the policy is not about an expansion flag, this will simply return a
319 * list with a single element, oneself. If the policy is for an expansion flag, the policy will
320 * get split into multiple policies applying to each flag the original flag expands to.
321 *
322 * <p>None of the flagPolicies returned should be on expansion flags.
323 */
ajurkowski69770672021-06-04 04:43:28 -0700324 private static ImmutableList<FlagPolicyWithContext> expandPolicy(
ccalvarin06e68742018-03-01 07:29:45 -0800325 FlagPolicyWithContext originalPolicy, OptionsParser parser, Level loglevel)
ccalvarin706bafe2017-03-30 17:34:04 +0000326 throws OptionsParsingException {
ajurkowski69770672021-06-04 04:43:28 -0700327 ImmutableList.Builder<FlagPolicyWithContext> expandedPolicies = ImmutableList.builder();
ccalvarin706bafe2017-03-30 17:34:04 +0000328
ccalvarin34a9fea2017-10-17 23:27:19 +0200329 boolean isExpansion = originalPolicy.description.isExpansion();
330 ImmutableList<ParsedOptionDescription> subflags =
331 parser.getExpansionValueDescriptions(
332 originalPolicy.description.getOptionDefinition(), originalPolicy.origin);
333
334 // If we have nothing to expand to, no need to do any further work.
335 if (subflags.isEmpty()) {
ccalvarin7cd9e882017-10-16 22:18:32 +0200336 return ImmutableList.of(originalPolicy);
337 }
ccalvarin706bafe2017-03-30 17:34:04 +0000338
janakrc9560212020-05-27 15:07:36 -0700339 // Log the expansion. This is only really useful for understanding the invocation policy itself.
340 logger.at(loglevel).log(
341 "Expanding %s on option %s to its %s: %s.",
342 originalPolicy.policy.getOperationCase(),
343 originalPolicy.policy.getFlagName(),
344 isExpansion ? "expansions" : "implied flags",
345 LazyArgs.lazy(
346 () ->
347 subflags.stream()
348 .map(f -> "--" + f.getOptionDefinition().getOptionName())
349 .collect(joining("; "))));
ccalvarin36109fc2017-04-05 15:18:51 +0000350
ccalvarinae1d0de2017-04-15 05:19:09 +0200351 // Repeated flags are special, and could set multiple times in an expansion, with the user
352 // expecting both values to be valid. Collect these separately.
ccalvarinca744822017-10-10 13:50:15 +0200353 Multimap<OptionDescription, ParsedOptionDescription> repeatableSubflagsInSetValues =
ccalvarinae1d0de2017-04-15 05:19:09 +0200354 ArrayListMultimap.create();
355
ccalvarin706bafe2017-03-30 17:34:04 +0000356 // Create a flag policy for the child that looks like the parent's policy "transferred" to its
357 // child. Note that this only makes sense for SetValue, when setting an expansion flag, or
358 // UseDefault, when preventing it from being set.
ccalvarinfb153cd2017-09-15 19:29:50 +0200359 for (ParsedOptionDescription currentSubflag : subflags) {
ccalvarinca744822017-10-10 13:50:15 +0200360 OptionDescription subflagOptionDescription =
ccalvarin34a9fea2017-10-17 23:27:19 +0200361 parser.getOptionDescription(currentSubflag.getOptionDefinition().getOptionName());
ccalvarinca744822017-10-10 13:50:15 +0200362
ccalvarin1dce0972017-09-11 20:03:02 +0200363 if (currentSubflag.getOptionDefinition().allowsMultiple()
ccalvarinca744822017-10-10 13:50:15 +0200364 && originalPolicy.policy.getOperationCase().equals(OperationCase.SET_VALUE)) {
365 repeatableSubflagsInSetValues.put(subflagOptionDescription, currentSubflag);
ccalvarinae1d0de2017-04-15 05:19:09 +0200366 } else {
ccalvarinca744822017-10-10 13:50:15 +0200367 FlagPolicyWithContext subflagAsPolicy =
368 getSingleValueSubflagAsPolicy(
369 subflagOptionDescription, currentSubflag, originalPolicy, isExpansion);
ccalvarinae1d0de2017-04-15 05:19:09 +0200370 // In case any of the expanded flags are themselves expansions, recurse.
ccalvarin34a9fea2017-10-17 23:27:19 +0200371 expandedPolicies.addAll(expandPolicy(subflagAsPolicy, parser, loglevel));
ccalvarin706bafe2017-03-30 17:34:04 +0000372 }
ccalvarin706bafe2017-03-30 17:34:04 +0000373 }
374
ccalvarinae1d0de2017-04-15 05:19:09 +0200375 // If there are any repeatable flag SetValues, deal with them together now.
376 // Note that expansion flags have no value, and so cannot have multiple values either.
377 // Skipping the recursion above is fine.
ccalvarinca744822017-10-10 13:50:15 +0200378 for (OptionDescription repeatableFlag : repeatableSubflagsInSetValues.keySet()) {
ccalvarinae1d0de2017-04-15 05:19:09 +0200379 int numValues = repeatableSubflagsInSetValues.get(repeatableFlag).size();
380 ArrayList<String> newValues = new ArrayList<>(numValues);
ccalvarin34a9fea2017-10-17 23:27:19 +0200381 ArrayList<OptionInstanceOrigin> origins = new ArrayList<>(numValues);
ccalvarinfb153cd2017-09-15 19:29:50 +0200382 for (ParsedOptionDescription setValue : repeatableSubflagsInSetValues.get(repeatableFlag)) {
ccalvarina8c0c8d2017-09-14 16:54:39 +0200383 newValues.add(setValue.getUnconvertedValue());
ccalvarin34a9fea2017-10-17 23:27:19 +0200384 origins.add(setValue.getOrigin());
ccalvarinae1d0de2017-04-15 05:19:09 +0200385 }
ccalvarin34a9fea2017-10-17 23:27:19 +0200386 // These options come from expanding a single policy, so they have effectively the same
387 // priority. They could have come from different expansions or implicit requirements in the
388 // recursive resolving of the option list, so just pick the first one. Do collapse the source
389 // strings though, in case there are different sources.
390 OptionInstanceOrigin arbitraryFirstOptionOrigin = origins.get(0);
391 OptionInstanceOrigin originOfSubflags =
392 new OptionInstanceOrigin(
393 arbitraryFirstOptionOrigin.getPriority(),
janakrc9560212020-05-27 15:07:36 -0700394 origins.stream()
ccalvarin34a9fea2017-10-17 23:27:19 +0200395 .map(OptionInstanceOrigin::getSource)
396 .distinct()
janakrc9560212020-05-27 15:07:36 -0700397 .collect(joining(", ")),
ccalvarin34a9fea2017-10-17 23:27:19 +0200398 arbitraryFirstOptionOrigin.getImplicitDependent(),
399 arbitraryFirstOptionOrigin.getExpandedFrom());
ccalvarin7cd9e882017-10-16 22:18:32 +0200400 expandedPolicies.add(
401 getSetValueSubflagAsPolicy(repeatableFlag, newValues, originOfSubflags, originalPolicy));
ccalvarinae1d0de2017-04-15 05:19:09 +0200402 }
403
404 // Don't add the original policy if it was an expansion flag, which have no value, but do add
405 // it if there was either no expansion or if it was a valued flag with implicit requirements.
ccalvarin706bafe2017-03-30 17:34:04 +0000406 if (!isExpansion) {
Googler7c7255e2017-06-27 20:05:20 +0200407 expandedPolicies.add(originalPolicy);
ccalvarin706bafe2017-03-30 17:34:04 +0000408 }
409
ajurkowski69770672021-06-04 04:43:28 -0700410 return expandedPolicies.build();
ccalvarin706bafe2017-03-30 17:34:04 +0000411 }
412
ccalvarinae1d0de2017-04-15 05:19:09 +0200413 /**
414 * Expand a SetValue flag policy on a repeatable flag. SetValue operations are the only flag
415 * policies that set the flag, and so interact with repeatable flags, flags that can be set
416 * multiple times, in subtle ways.
417 *
ccalvarinca744822017-10-10 13:50:15 +0200418 * @param subflagDesc, the description of the flag the SetValue'd expansion flag expands to.
ccalvarinae1d0de2017-04-15 05:19:09 +0200419 * @param subflagValue, the values that the SetValue'd expansion flag expands to for this flag.
ccalvarinae1d0de2017-04-15 05:19:09 +0200420 * @param originalPolicy, the original policy on the expansion flag.
421 * @return the flag policy for the subflag given, this will be part of the expanded form of the
ccalvarin1dce0972017-09-11 20:03:02 +0200422 * SetValue policy on the original flag.
ccalvarinae1d0de2017-04-15 05:19:09 +0200423 */
ccalvarinca744822017-10-10 13:50:15 +0200424 private static FlagPolicyWithContext getSetValueSubflagAsPolicy(
425 OptionDescription subflagDesc,
426 List<String> subflagValue,
ccalvarin7cd9e882017-10-16 22:18:32 +0200427 OptionInstanceOrigin subflagOrigin,
Googler4442bb92021-10-15 09:15:28 -0700428 FlagPolicyWithContext originalPolicy) {
Googler199e70c2020-10-08 19:57:27 -0700429 // Some checks.
ccalvarinca744822017-10-10 13:50:15 +0200430 OptionDefinition subflag = subflagDesc.getOptionDefinition();
431 Verify.verify(originalPolicy.policy.getOperationCase().equals(OperationCase.SET_VALUE));
ccalvarin1dce0972017-09-11 20:03:02 +0200432 if (!subflag.allowsMultiple()) {
ccalvarinae1d0de2017-04-15 05:19:09 +0200433 Verify.verify(subflagValue.size() <= 1);
434 }
435
436 // Flag value from the expansion, overridability from the original policy, unless the flag is
437 // repeatable, in which case we care about appendability, not overridability.
438 SetValue.Builder setValueExpansion = SetValue.newBuilder();
439 for (String value : subflagValue) {
440 setValueExpansion.addFlagValue(value);
441 }
Googler4442bb92021-10-15 09:15:28 -0700442 if (subflag.allowsMultiple()) {
443 setValueExpansion.setAppend(originalPolicy.policy.getSetValue().getOverridable());
444 } else {
445 setValueExpansion.setOverridable(originalPolicy.policy.getSetValue().getOverridable());
ccalvarinae1d0de2017-04-15 05:19:09 +0200446 }
447
448 // Commands from the original policy, flag name of the expansion
ccalvarinca744822017-10-10 13:50:15 +0200449 return new FlagPolicyWithContext(
450 FlagPolicy.newBuilder()
451 .addAllCommands(originalPolicy.policy.getCommandsList())
452 .setFlagName(subflag.getOptionName())
453 .setSetValue(setValueExpansion)
454 .build(),
ccalvarin7cd9e882017-10-16 22:18:32 +0200455 subflagDesc,
456 subflagOrigin);
ccalvarinae1d0de2017-04-15 05:19:09 +0200457 }
458
459 /**
460 * For an expansion flag in an invocation policy, each flag it expands to must be given a
461 * corresponding policy.
462 */
ccalvarinca744822017-10-10 13:50:15 +0200463 private static FlagPolicyWithContext getSingleValueSubflagAsPolicy(
464 OptionDescription subflagContext,
465 ParsedOptionDescription currentSubflag,
466 FlagPolicyWithContext originalPolicy,
467 boolean isExpansion)
Googler7c7255e2017-06-27 20:05:20 +0200468 throws OptionsParsingException {
ccalvarinca744822017-10-10 13:50:15 +0200469 FlagPolicyWithContext subflagAsPolicy = null;
470 switch (originalPolicy.policy.getOperationCase()) {
ccalvarinae1d0de2017-04-15 05:19:09 +0200471 case SET_VALUE:
ccalvarin1dce0972017-09-11 20:03:02 +0200472 if (currentSubflag.getOptionDefinition().allowsMultiple()) {
ccalvarinc331f242017-08-03 02:12:48 +0200473 throw new AssertionError(
474 "SetValue subflags with allowMultiple should have been dealt with separately and "
475 + "accumulated into a single FlagPolicy.");
476 }
477 // Accept null originalValueStrings, they are expected when the subflag is also an expansion
478 // flag.
479 List<String> subflagValue;
ccalvarina8c0c8d2017-09-14 16:54:39 +0200480 if (currentSubflag.getUnconvertedValue() == null) {
ccalvarinc331f242017-08-03 02:12:48 +0200481 subflagValue = ImmutableList.of();
482 } else {
ccalvarina8c0c8d2017-09-14 16:54:39 +0200483 subflagValue = ImmutableList.of(currentSubflag.getUnconvertedValue());
ccalvarinc331f242017-08-03 02:12:48 +0200484 }
ccalvarin7cd9e882017-10-16 22:18:32 +0200485 subflagAsPolicy =
486 getSetValueSubflagAsPolicy(
487 subflagContext, subflagValue, currentSubflag.getOrigin(), originalPolicy);
ccalvarinae1d0de2017-04-15 05:19:09 +0200488 break;
489
490 case USE_DEFAULT:
491 // Commands from the original policy, flag name of the expansion
492 subflagAsPolicy =
ccalvarinca744822017-10-10 13:50:15 +0200493 new FlagPolicyWithContext(
494 FlagPolicy.newBuilder()
495 .addAllCommands(originalPolicy.policy.getCommandsList())
496 .setFlagName(currentSubflag.getOptionDefinition().getOptionName())
497 .setUseDefault(UseDefault.getDefaultInstance())
498 .build(),
ccalvarin7cd9e882017-10-16 22:18:32 +0200499 subflagContext,
500 currentSubflag.getOrigin());
ccalvarinae1d0de2017-04-15 05:19:09 +0200501 break;
502
503 case ALLOW_VALUES:
504 if (isExpansion) {
ccalvarinca744822017-10-10 13:50:15 +0200505 throwAllowValuesOnExpansionFlagException(originalPolicy.policy.getFlagName());
ccalvarinae1d0de2017-04-15 05:19:09 +0200506 }
507 // If this flag is an implicitRequirement, and some values for the parent flag are
508 // allowed, nothing needs to happen on the implicitRequirement that is set for all
509 // values of the flag.
510 break;
511
512 case DISALLOW_VALUES:
513 if (isExpansion) {
ccalvarinca744822017-10-10 13:50:15 +0200514 throwDisallowValuesOnExpansionFlagException(originalPolicy.policy.getFlagName());
ccalvarinae1d0de2017-04-15 05:19:09 +0200515 }
516 // If this flag is an implicitRequirement, and some values for the parent flag are
517 // disallowed, that implies that all others are allowed, so nothing needs to happen
518 // on the implicitRequirement that is set for all values of the parent flag.
519 break;
520
521 case OPERATION_NOT_SET:
ccalvarinca744822017-10-10 13:50:15 +0200522 throw new PolicyOperationNotSetException(originalPolicy.policy.getFlagName());
ccalvarinae1d0de2017-04-15 05:19:09 +0200523
524 default:
525 return null;
526 }
527 return subflagAsPolicy;
528 }
529
Alex Humesky2f3f4cf2015-09-29 01:42:00 +0000530 private static void applySetValueOperation(
531 OptionsParser parser,
ccalvarinca744822017-10-10 13:50:15 +0200532 FlagPolicyWithContext flagPolicy,
Alex Humesky2f3f4cf2015-09-29 01:42:00 +0000533 OptionValueDescription valueDescription,
ccalvarin334d2f12017-10-05 16:39:42 +0200534 Level loglevel)
Janak Ramakrishnanb92c0972016-03-23 16:47:13 +0000535 throws OptionsParsingException {
ccalvarinca744822017-10-10 13:50:15 +0200536 SetValue setValue = flagPolicy.policy.getSetValue();
537 OptionDefinition optionDefinition = flagPolicy.description.getOptionDefinition();
Alex Humesky2f3f4cf2015-09-29 01:42:00 +0000538
539 // SetValue.flag_value must have at least 1 value.
540 if (setValue.getFlagValueCount() == 0) {
Janak Ramakrishnanb92c0972016-03-23 16:47:13 +0000541 throw new OptionsParsingException(
542 String.format(
ccalvarin7cd9e882017-10-16 22:18:32 +0200543 "SetValue operation from invocation policy for %s does not have a value",
544 optionDefinition));
Alex Humesky2f3f4cf2015-09-29 01:42:00 +0000545 }
Luis Fernando Pino Duqueb1b28b62016-02-25 14:25:19 +0000546
Alex Humesky2f3f4cf2015-09-29 01:42:00 +0000547 // Flag must allow multiple values if multiple values are specified by the policy.
ccalvarin00443492017-08-30 00:23:40 +0200548 if (setValue.getFlagValueCount() > 1
ccalvarinca744822017-10-10 13:50:15 +0200549 && !flagPolicy.description.getOptionDefinition().allowsMultiple()) {
Janak Ramakrishnanb92c0972016-03-23 16:47:13 +0000550 throw new OptionsParsingException(
551 String.format(
ccalvarin7cd9e882017-10-16 22:18:32 +0200552 "SetValue operation from invocation policy sets multiple values for %s which "
Janak Ramakrishnanb92c0972016-03-23 16:47:13 +0000553 + "does not allow multiple values",
ccalvarin7cd9e882017-10-16 22:18:32 +0200554 optionDefinition));
Alex Humesky2f3f4cf2015-09-29 01:42:00 +0000555 }
Luis Fernando Pino Duqueb1b28b62016-02-25 14:25:19 +0000556
Googler4442bb92021-10-15 09:15:28 -0700557 if (setValue.getOverridable() && valueDescription != null) {
558 // The user set the value for the flag but the flag policy is overridable, so keep the user's
559 // value.
560 logger.at(loglevel).log(
561 "Keeping value '%s' from source '%s' for %s because the invocation policy specifying "
562 + "the value(s) '%s' is overridable",
563 valueDescription.getValue(),
564 valueDescription.getSourceString(),
565 optionDefinition,
566 setValue.getFlagValueList());
567 } else {
568
569 if (!setValue.getAppend()) {
Alex Humesky05950072016-05-09 18:38:55 +0000570 // Clear the value in case the flag is a repeated flag so that values don't accumulate.
ccalvarinca744822017-10-10 13:50:15 +0200571 parser.clearValue(flagPolicy.description.getOptionDefinition());
ajurkowski6d7557a2021-10-15 07:48:21 -0700572 }
ccalvarin7cd9e882017-10-16 22:18:32 +0200573
Googler4442bb92021-10-15 09:15:28 -0700574 // Set all the flag values from the policy.
575 for (String flagValue : setValue.getFlagValueList()) {
576 if (valueDescription == null) {
577 logger.at(loglevel).log(
578 "Setting value for %s from invocation policy to '%s', overriding the default value "
579 + "'%s'",
580 optionDefinition, flagValue, optionDefinition.getDefaultValue());
581 } else {
582 logger.at(loglevel).log(
583 "Setting value for %s from invocation policy to '%s', overriding value '%s' from "
584 + "'%s'",
585 optionDefinition,
586 flagValue,
587 valueDescription.getValue(),
588 valueDescription.getSourceString());
589 }
590
ajurkowskie88245d2021-06-10 18:00:38 -0700591 parser.setOptionValueAtSpecificPriorityWithoutExpansion(
592 flagPolicy.origin, optionDefinition, flagValue);
Googler4442bb92021-10-15 09:15:28 -0700593 }
Alex Humesky2f3f4cf2015-09-29 01:42:00 +0000594 }
595 }
596
Alex Humeskyb1f55c82016-10-25 01:06:39 +0000597 private static void applyUseDefaultOperation(
ccalvarin334d2f12017-10-05 16:39:42 +0200598 OptionsParser parser, String policyType, OptionDefinition option, Level loglevel)
ccalvarin1dce0972017-09-11 20:03:02 +0200599 throws OptionsParsingException {
600 OptionValueDescription clearedValueDescription = parser.clearValue(option);
ccalvarin0e02f532017-04-04 18:31:27 +0000601 if (clearedValueDescription != null) {
602 // Log the removed value.
ccalvarin1dce0972017-09-11 20:03:02 +0200603 String clearedFlagName = clearedValueDescription.getOptionDefinition().getOptionName();
ccalvarinca744822017-10-10 13:50:15 +0200604 Object clearedFlagDefaultValue =
605 clearedValueDescription.getOptionDefinition().getDefaultValue();
janakrc9560212020-05-27 15:07:36 -0700606 logger.at(loglevel).log(
607 "Using default value '%s' for flag '%s' as specified by %s invocation policy, "
608 + "overriding original value '%s' from '%s'",
609 clearedFlagDefaultValue,
610 clearedFlagName,
611 policyType,
612 clearedValueDescription.getValue(),
613 clearedValueDescription.getSourceString());
Alex Humesky2f3f4cf2015-09-29 01:42:00 +0000614 }
615 }
616
ccalvarin334d2f12017-10-05 16:39:42 +0200617 /** Checks the user's flag values against a filtering function. */
Alex Humeskyc0d27692016-02-03 00:52:04 +0000618 private abstract static class FilterValueOperation {
Alex Humesky2f3f4cf2015-09-29 01:42:00 +0000619
ccalvarin334d2f12017-10-05 16:39:42 +0200620 private static final class AllowValueOperation extends FilterValueOperation {
621 AllowValueOperation(Level loglevel) {
622 super("Allow", loglevel);
623 }
Alex Humeskyc0d27692016-02-03 00:52:04 +0000624
ccalvarin334d2f12017-10-05 16:39:42 +0200625 @Override
626 boolean isFlagValueAllowed(Set<Object> convertedPolicyValues, Object value) {
627 return convertedPolicyValues.contains(value);
628 }
629 }
630
631 private static final class DisallowValueOperation extends FilterValueOperation {
632 DisallowValueOperation(Level loglevel) {
633 super("Disalllow", loglevel);
634 }
635
636 @Override
637 boolean isFlagValueAllowed(Set<Object> convertedPolicyValues, Object value) {
638 // In a disallow operation, the values that the flag policy specifies are not allowed,
639 // so the value is allowed if the set of policy values does not contain the current
640 // flag value.
641 return !convertedPolicyValues.contains(value);
642 }
643 }
Luis Fernando Pino Duqueb1b28b62016-02-25 14:25:19 +0000644
Alex Humeskyc0d27692016-02-03 00:52:04 +0000645 private final String policyType;
ccalvarin334d2f12017-10-05 16:39:42 +0200646 private final Level loglevel;
Alex Humeskyc0d27692016-02-03 00:52:04 +0000647
ccalvarin334d2f12017-10-05 16:39:42 +0200648 FilterValueOperation(String policyType, Level loglevel) {
Alex Humeskyc0d27692016-02-03 00:52:04 +0000649 this.policyType = policyType;
ccalvarin334d2f12017-10-05 16:39:42 +0200650 this.loglevel = loglevel;
Alex Humesky2f3f4cf2015-09-29 01:42:00 +0000651 }
652
Alex Humeskyc0d27692016-02-03 00:52:04 +0000653 /**
654 * Determines if the given value is allowed.
655 *
656 * @param convertedPolicyValues The values given from the FlagPolicy, converted to real objects.
657 * @param value The user value of the flag.
658 * @return True if the value should be allowed, false if it should not.
659 */
Alex Humeskyb1f55c82016-10-25 01:06:39 +0000660 abstract boolean isFlagValueAllowed(Set<Object> convertedPolicyValues, Object value);
Luis Fernando Pino Duqueb1b28b62016-02-25 14:25:19 +0000661
Alex Humeskyc0d27692016-02-03 00:52:04 +0000662 void apply(
663 OptionsParser parser,
ccalvarin7cd9e882017-10-16 22:18:32 +0200664 OptionInstanceOrigin origin,
Alex Humeskyc0d27692016-02-03 00:52:04 +0000665 List<String> policyValues,
Alex Humeskyb1f55c82016-10-25 01:06:39 +0000666 String newValue,
667 boolean useDefault,
Alex Humeskyc0d27692016-02-03 00:52:04 +0000668 OptionValueDescription valueDescription,
Janak Ramakrishnanb92c0972016-03-23 16:47:13 +0000669 OptionDescription optionDescription)
670 throws OptionsParsingException {
ccalvarin1dce0972017-09-11 20:03:02 +0200671 OptionDefinition optionDefinition = optionDescription.getOptionDefinition();
Alex Humeskyc0d27692016-02-03 00:52:04 +0000672 // Convert all the allowed values from strings to real objects using the options'
673 // converters so that they can be checked for equality using real .equals() instead
674 // of string comparison. For example, "--foo=0", "--foo=false", "--nofoo", and "-f-"
675 // (if the option has an abbreviation) are all equal for boolean flags. Plus converters
676 // can be arbitrarily complex.
Jonathan Bluett-Duncan0df3ddbd2017-08-09 11:13:54 +0200677 Set<Object> convertedPolicyValues = new HashSet<>();
Alex Humeskyc0d27692016-02-03 00:52:04 +0000678 for (String value : policyValues) {
ccalvarin1dce0972017-09-11 20:03:02 +0200679 Object convertedValue = optionDefinition.getConverter().convert(value);
Alex Humesky64155872016-12-16 22:12:52 +0000680 // Some converters return lists, and if the flag is a repeatable flag, the items in the
681 // list from the converter should be added, and not the list itself. Otherwise the items
682 // from invocation policy will be compared to lists, which will never work.
683 // See OptionsParserImpl.ParsedOptionEntry.addValue.
ccalvarin1dce0972017-09-11 20:03:02 +0200684 if (optionDefinition.allowsMultiple() && convertedValue instanceof List<?>) {
Alex Humesky64155872016-12-16 22:12:52 +0000685 convertedPolicyValues.addAll((List<?>) convertedValue);
686 } else {
ccalvarin1dce0972017-09-11 20:03:02 +0200687 convertedPolicyValues.add(optionDefinition.getConverter().convert(value));
Alex Humesky64155872016-12-16 22:12:52 +0000688 }
Alex Humeskyc0d27692016-02-03 00:52:04 +0000689 }
690
Alex Humeskyb1f55c82016-10-25 01:06:39 +0000691 // Check that if the default value of the flag is disallowed by the policy, that the policy
ccalvarin06e68742018-03-01 07:29:45 -0800692 // does not also set use_default. Otherwise the default value would still be set if the
Alex Humeskyb1f55c82016-10-25 01:06:39 +0000693 // user uses a disallowed value. This doesn't apply to repeatable flags since the default
ccalvarin06e68742018-03-01 07:29:45 -0800694 // value for repeatable flags is always the empty list. It also doesn't apply to flags that
695 // are null by default, since these flags' default value is not parsed by the converter, so
696 // there is no guarantee that there exists an accepted user-input value that would also set
697 // the value to NULL. In these cases, we assume that "unset" is a distinct value that is
698 // always allowed.
699 if (!optionDescription.getOptionDefinition().allowsMultiple()
700 && !optionDescription.getOptionDefinition().isSpecialNullDefault()) {
Alex Humeskyb1f55c82016-10-25 01:06:39 +0000701 boolean defaultValueAllowed =
ccalvarin00443492017-08-30 00:23:40 +0200702 isFlagValueAllowed(
703 convertedPolicyValues, optionDescription.getOptionDefinition().getDefaultValue());
Alex Humeskyb1f55c82016-10-25 01:06:39 +0000704 if (!defaultValueAllowed && useDefault) {
705 throw new OptionsParsingException(
706 String.format(
ccalvarin7cd9e882017-10-16 22:18:32 +0200707 "%sValues policy disallows the default value '%s' for %s but also specifies to "
708 + "use the default value",
709 policyType, optionDefinition.getDefaultValue(), optionDefinition));
Alex Humeskyb1f55c82016-10-25 01:06:39 +0000710 }
711 }
712
Alex Humeskyc0d27692016-02-03 00:52:04 +0000713 if (valueDescription == null) {
714 // Nothing has set the value yet, so check that the default value from the flag's
715 // definition is allowed. The else case below (i.e. valueDescription is not null) checks for
716 // the flag allowing multiple values, however, flags that allow multiple values cannot have
717 // default values, and their value is always the empty list if they haven't been specified,
718 // which is why new_default_value is not a repeated field.
ccalvarin7cd9e882017-10-16 22:18:32 +0200719 checkDefaultValue(
720 parser, origin, optionDescription, policyValues, newValue, convertedPolicyValues);
Alex Humeskyc0d27692016-02-03 00:52:04 +0000721 } else {
722 checkUserValue(
Alex Humeskyb1f55c82016-10-25 01:06:39 +0000723 parser,
ccalvarin7cd9e882017-10-16 22:18:32 +0200724 origin,
ccalvarin1dce0972017-09-11 20:03:02 +0200725 optionDescription,
726 valueDescription,
Alex Humeskyb1f55c82016-10-25 01:06:39 +0000727 policyValues,
728 newValue,
729 useDefault,
Alex Humeskyb1f55c82016-10-25 01:06:39 +0000730 convertedPolicyValues);
Alex Humeskyc0d27692016-02-03 00:52:04 +0000731 }
732 }
Luis Fernando Pino Duqueb1b28b62016-02-25 14:25:19 +0000733
Alex Humeskyc0d27692016-02-03 00:52:04 +0000734 void checkDefaultValue(
735 OptionsParser parser,
ccalvarin7cd9e882017-10-16 22:18:32 +0200736 OptionInstanceOrigin origin,
ccalvarin1dce0972017-09-11 20:03:02 +0200737 OptionDescription optionDescription,
Alex Humeskyc0d27692016-02-03 00:52:04 +0000738 List<String> policyValues,
Alex Humeskyb1f55c82016-10-25 01:06:39 +0000739 String newValue,
Janak Ramakrishnanb92c0972016-03-23 16:47:13 +0000740 Set<Object> convertedPolicyValues)
741 throws OptionsParsingException {
Alex Humeskyc0d27692016-02-03 00:52:04 +0000742
ccalvarin1dce0972017-09-11 20:03:02 +0200743 OptionDefinition optionDefinition = optionDescription.getOptionDefinition();
ccalvarin06e68742018-03-01 07:29:45 -0800744 if (optionDefinition.isSpecialNullDefault()) {
745 // Do nothing, the unset value by definition cannot be set. In option filtering operations,
746 // the value is being filtered, but the value that is `no value` passes any filter.
747 // Otherwise, there is no way to "usedefault" on one of these options that has no value by
748 // default.
749 } else if (!isFlagValueAllowed(convertedPolicyValues, optionDefinition.getDefaultValue())) {
Alex Humeskyb1f55c82016-10-25 01:06:39 +0000750 if (newValue != null) {
ccalvarin7cd9e882017-10-16 22:18:32 +0200751 // Use the default value from the policy, since the original default is not allowed
janakrc9560212020-05-27 15:07:36 -0700752 logger.at(loglevel).log(
753 "Overriding default value '%s' for %s with value '%s' specified by invocation "
754 + "policy. %sed values are: %s",
755 optionDefinition.getDefaultValue(),
756 optionDefinition,
757 newValue,
758 policyType,
759 policyValues);
ccalvarin1dce0972017-09-11 20:03:02 +0200760 parser.clearValue(optionDefinition);
ajurkowskie88245d2021-06-10 18:00:38 -0700761 parser.setOptionValueAtSpecificPriorityWithoutExpansion(
762 origin, optionDefinition, newValue);
Alex Humesky2f3f4cf2015-09-29 01:42:00 +0000763 } else {
Alex Humeskyb1f55c82016-10-25 01:06:39 +0000764 // The operation disallows the default value, but doesn't supply a new value.
Janak Ramakrishnanb92c0972016-03-23 16:47:13 +0000765 throw new OptionsParsingException(
766 String.format(
ccalvarin7cd9e882017-10-16 22:18:32 +0200767 "Default flag value '%s' for %s is not allowed by invocation policy, but "
ccalvarin3ab171a2017-09-19 16:36:49 +0200768 + "the policy does not provide a new value. %sed values are: %s",
ccalvarin00443492017-08-30 00:23:40 +0200769 optionDescription.getOptionDefinition().getDefaultValue(),
ccalvarin7cd9e882017-10-16 22:18:32 +0200770 optionDefinition,
Janak Ramakrishnanb92c0972016-03-23 16:47:13 +0000771 policyType,
772 policyValues));
Alex Humesky2f3f4cf2015-09-29 01:42:00 +0000773 }
774 }
Alex Humeskyc0d27692016-02-03 00:52:04 +0000775 }
Luis Fernando Pino Duqueb1b28b62016-02-25 14:25:19 +0000776
Alex Humeskyc0d27692016-02-03 00:52:04 +0000777 void checkUserValue(
Alex Humeskyb1f55c82016-10-25 01:06:39 +0000778 OptionsParser parser,
ccalvarin7cd9e882017-10-16 22:18:32 +0200779 OptionInstanceOrigin origin,
ccalvarin1dce0972017-09-11 20:03:02 +0200780 OptionDescription optionDescription,
781 OptionValueDescription valueDescription,
Alex Humeskyc0d27692016-02-03 00:52:04 +0000782 List<String> policyValues,
Alex Humeskyb1f55c82016-10-25 01:06:39 +0000783 String newValue,
784 boolean useDefault,
Janak Ramakrishnanb92c0972016-03-23 16:47:13 +0000785 Set<Object> convertedPolicyValues)
786 throws OptionsParsingException {
ccalvarin1dce0972017-09-11 20:03:02 +0200787 OptionDefinition option = optionDescription.getOptionDefinition();
ccalvarin00443492017-08-30 00:23:40 +0200788 if (optionDescription.getOptionDefinition().allowsMultiple()) {
Alex Humeskyc0d27692016-02-03 00:52:04 +0000789 // allowMultiple requires that the type of the option be List<T>, so cast from Object
790 // to List<?>.
Alex Humeskyb1f55c82016-10-25 01:06:39 +0000791 List<?> optionValues = (List<?>) valueDescription.getValue();
792 for (Object value : optionValues) {
793 if (!isFlagValueAllowed(convertedPolicyValues, value)) {
794 if (useDefault) {
ccalvarin334d2f12017-10-05 16:39:42 +0200795 applyUseDefaultOperation(parser, policyType + "Values", option, loglevel);
Alex Humeskyb1f55c82016-10-25 01:06:39 +0000796 } else {
797 throw new OptionsParsingException(
798 String.format(
ccalvarin7cd9e882017-10-16 22:18:32 +0200799 "Flag value '%s' for %s is not allowed by invocation policy. %sed values "
800 + "are: %s",
801 value, option, policyType, policyValues));
Alex Humeskyb1f55c82016-10-25 01:06:39 +0000802 }
803 }
804 }
Alex Humesky2f3f4cf2015-09-29 01:42:00 +0000805
Alex Humeskyb1f55c82016-10-25 01:06:39 +0000806 } else {
807
808 if (!isFlagValueAllowed(convertedPolicyValues, valueDescription.getValue())) {
809 if (newValue != null) {
janakrc9560212020-05-27 15:07:36 -0700810 logger.at(loglevel).log(
811 "Overriding disallowed value '%s' for %s with value '%s' "
812 + "specified by invocation policy. %sed values are: %s",
813 valueDescription.getValue(), option, newValue, policyType, policyValues);
ccalvarin1dce0972017-09-11 20:03:02 +0200814 parser.clearValue(option);
ajurkowskie88245d2021-06-10 18:00:38 -0700815 parser.setOptionValueAtSpecificPriorityWithoutExpansion(origin, option, newValue);
Alex Humeskyb1f55c82016-10-25 01:06:39 +0000816 } else if (useDefault) {
ccalvarin334d2f12017-10-05 16:39:42 +0200817 applyUseDefaultOperation(parser, policyType + "Values", option, loglevel);
Alex Humeskyb1f55c82016-10-25 01:06:39 +0000818 } else {
819 throw new OptionsParsingException(
820 String.format(
ccalvarin7cd9e882017-10-16 22:18:32 +0200821 "Flag value '%s' for %s is not allowed by invocation policy and the "
Alex Humeskyb1f55c82016-10-25 01:06:39 +0000822 + "policy does not specify a new value. %sed values are: %s",
ccalvarin7cd9e882017-10-16 22:18:32 +0200823 valueDescription.getValue(), option, policyType, policyValues));
Alex Humeskyb1f55c82016-10-25 01:06:39 +0000824 }
Alex Humesky2f3f4cf2015-09-29 01:42:00 +0000825 }
826 }
827 }
828 }
Alex Humesky2f3f4cf2015-09-29 01:42:00 +0000829}