blob: 136769b1d0c56967cc7223a2b70bd15622284ef4 [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
janakrc9560212020-05-27 15:07:36 -070016import static java.util.stream.Collectors.joining;
17
Alex Humesky2f3f4cf2015-09-29 01:42:00 +000018import com.google.common.base.Verify;
ccalvarinae1d0de2017-04-15 05:19:09 +020019import com.google.common.collect.ArrayListMultimap;
ccalvarin706bafe2017-03-30 17:34:04 +000020import com.google.common.collect.ImmutableList;
Janak Ramakrishnane9cd0f32016-04-29 22:46:16 +000021import com.google.common.collect.ImmutableSet;
ccalvarinae1d0de2017-04-15 05:19:09 +020022import com.google.common.collect.Multimap;
janakrc9560212020-05-27 15:07:36 -070023import com.google.common.flogger.GoogleLogger;
24import com.google.common.flogger.LazyArgs;
Alex Humesky2f3f4cf2015-09-29 01:42:00 +000025import com.google.devtools.build.lib.runtime.proto.InvocationPolicyOuterClass.AllowValues;
26import com.google.devtools.build.lib.runtime.proto.InvocationPolicyOuterClass.DisallowValues;
27import com.google.devtools.build.lib.runtime.proto.InvocationPolicyOuterClass.FlagPolicy;
ccalvarinae1d0de2017-04-15 05:19:09 +020028import com.google.devtools.build.lib.runtime.proto.InvocationPolicyOuterClass.FlagPolicy.OperationCase;
Alex Humesky2f3f4cf2015-09-29 01:42:00 +000029import com.google.devtools.build.lib.runtime.proto.InvocationPolicyOuterClass.InvocationPolicy;
30import com.google.devtools.build.lib.runtime.proto.InvocationPolicyOuterClass.SetValue;
ajurkowskiecab2972021-06-17 14:26:34 -070031import com.google.devtools.build.lib.runtime.proto.InvocationPolicyOuterClass.SetValue.Behavior;
ccalvarin706bafe2017-03-30 17:34:04 +000032import com.google.devtools.build.lib.runtime.proto.InvocationPolicyOuterClass.UseDefault;
ccalvarin7cd9e882017-10-16 22:18:32 +020033import com.google.devtools.common.options.OptionPriority.PriorityCategory;
Alex Humesky2f3f4cf2015-09-29 01:42:00 +000034import com.google.devtools.common.options.OptionsParser.OptionDescription;
ccalvarin706bafe2017-03-30 17:34:04 +000035import java.util.ArrayList;
ccalvarinefc9b632017-05-03 19:05:22 +020036import java.util.Collections;
37import java.util.HashMap;
Jonathan Bluett-Duncan0df3ddbd2017-08-09 11:13:54 +020038import java.util.HashSet;
Alex Humesky2f3f4cf2015-09-29 01:42:00 +000039import java.util.List;
ccalvarinefc9b632017-05-03 19:05:22 +020040import java.util.Map;
Alex Humesky2f3f4cf2015-09-29 01:42:00 +000041import java.util.Set;
Janak Ramakrishnanb40cbaa2016-11-18 00:20:45 +000042import java.util.logging.Level;
Alex Humesky2f3f4cf2015-09-29 01:42:00 +000043import javax.annotation.Nullable;
44
45/**
ccalvarin334d2f12017-10-05 16:39:42 +020046 * Enforces the {@link FlagPolicy}s (from an {@link InvocationPolicy} proto) on an {@link
47 * OptionsParser} by validating and changing the flag values in the given {@link OptionsParser}.
Alex Humesky2f3f4cf2015-09-29 01:42:00 +000048 *
49 * <p>"Flag" and "Option" are used interchangeably in this file.
50 */
51public final class InvocationPolicyEnforcer {
janakrc9560212020-05-27 15:07:36 -070052 private static final GoogleLogger logger = GoogleLogger.forEnclosingClass();
Alex Humeskyc0d27692016-02-03 00:52:04 +000053
ccalvarin5fe8e662017-09-14 15:56:43 +020054 private static final String INVOCATION_POLICY_SOURCE = "Invocation policy";
Janak Ramakrishnanb92c0972016-03-23 16:47:13 +000055 @Nullable private final InvocationPolicy invocationPolicy;
ccalvarin334d2f12017-10-05 16:39:42 +020056 private final Level loglevel;
Alex Humesky2f3f4cf2015-09-29 01:42:00 +000057
Alex Humeskyc0d27692016-02-03 00:52:04 +000058 /**
59 * Creates an InvocationPolicyEnforcer that enforces the given policy.
60 *
61 * @param invocationPolicy the policy to enforce. A null policy means this enforcer will do
62 * nothing in calls to enforce().
63 */
Alex Humesky2f3f4cf2015-09-29 01:42:00 +000064 public InvocationPolicyEnforcer(@Nullable InvocationPolicy invocationPolicy) {
ccalvarin334d2f12017-10-05 16:39:42 +020065 this(invocationPolicy, Level.FINE);
66 }
67
68 /**
69 * Creates an InvocationPolicyEnforcer that enforces the given policy.
70 *
71 * @param invocationPolicy the policy to enforce. A null policy means this enforcer will do
72 * nothing in calls to enforce().
73 * @param loglevel the level at which to log informational statements. Warnings and errors will
74 * still be logged at the appropriate level.
75 */
76 public InvocationPolicyEnforcer(@Nullable InvocationPolicy invocationPolicy, Level loglevel) {
Alex Humesky2f3f4cf2015-09-29 01:42:00 +000077 this.invocationPolicy = invocationPolicy;
ccalvarin334d2f12017-10-05 16:39:42 +020078 this.loglevel = loglevel;
Alex Humesky2f3f4cf2015-09-29 01:42:00 +000079 }
80
ccalvarinca744822017-10-10 13:50:15 +020081 private static final class FlagPolicyWithContext {
82 private final FlagPolicy policy;
83 private final OptionDescription description;
ccalvarin7cd9e882017-10-16 22:18:32 +020084 private final OptionInstanceOrigin origin;
ccalvarinca744822017-10-10 13:50:15 +020085
ccalvarin7cd9e882017-10-16 22:18:32 +020086 public FlagPolicyWithContext(
87 FlagPolicy policy, OptionDescription description, OptionInstanceOrigin origin) {
ccalvarinca744822017-10-10 13:50:15 +020088 this.policy = policy;
89 this.description = description;
ccalvarin7cd9e882017-10-16 22:18:32 +020090 this.origin = origin;
ccalvarinca744822017-10-10 13:50:15 +020091 }
92 }
93
ajurkowskiec66ded2021-10-27 10:39:52 -070094 @Nullable
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
ccalvarinefc9b632017-05-03 19:05:22 +0200124 // The effective policy returned is expanded, filtered for applicable commands, and cleaned of
125 // redundancies and conflicts.
ccalvarinca744822017-10-10 13:50:15 +0200126 List<FlagPolicyWithContext> effectivePolicies =
ccalvarin334d2f12017-10-05 16:39:42 +0200127 getEffectivePolicies(invocationPolicy, parser, command, loglevel);
ccalvarinefc9b632017-05-03 19:05:22 +0200128
ccalvarinca744822017-10-10 13:50:15 +0200129 for (FlagPolicyWithContext flagPolicy : effectivePolicies) {
130 String flagName = flagPolicy.policy.getFlagName();
Alex Humesky2f3f4cf2015-09-29 01:42:00 +0000131
Alex Humesky2f3f4cf2015-09-29 01:42:00 +0000132 OptionValueDescription valueDescription;
133 try {
134 valueDescription = parser.getOptionValueDescription(flagName);
135 } catch (IllegalArgumentException e) {
136 // This flag doesn't exist. We are deliberately lenient if the flag policy has a flag
137 // 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 +0000138 // new policies can use the new flags without worrying about older versions of Bazel.
janakrc9560212020-05-27 15:07:36 -0700139 logger.at(loglevel).log(
140 "Flag '%s' specified by invocation policy does not exist", flagName);
Alex Humesky2f3f4cf2015-09-29 01:42:00 +0000141 continue;
142 }
143
ccalvarin5fe8e662017-09-14 15:56:43 +0200144 // getOptionDescription() will return null if the option does not exist, however
Alex Humesky2f3f4cf2015-09-29 01:42:00 +0000145 // getOptionValueDescription() above would have thrown an IllegalArgumentException if that
146 // were the case.
ccalvarinca744822017-10-10 13:50:15 +0200147 Verify.verifyNotNull(flagPolicy.description);
Alex Humesky2f3f4cf2015-09-29 01:42:00 +0000148
ccalvarinca744822017-10-10 13:50:15 +0200149 switch (flagPolicy.policy.getOperationCase()) {
Alex Humesky2f3f4cf2015-09-29 01:42:00 +0000150 case SET_VALUE:
ccalvarinca744822017-10-10 13:50:15 +0200151 applySetValueOperation(parser, flagPolicy, valueDescription, loglevel);
Alex Humesky2f3f4cf2015-09-29 01:42:00 +0000152 break;
153
154 case USE_DEFAULT:
ccalvarin334d2f12017-10-05 16:39:42 +0200155 applyUseDefaultOperation(
ccalvarinca744822017-10-10 13:50:15 +0200156 parser, "UseDefault", flagPolicy.description.getOptionDefinition(), loglevel);
Alex Humesky2f3f4cf2015-09-29 01:42:00 +0000157 break;
158
159 case ALLOW_VALUES:
ccalvarinca744822017-10-10 13:50:15 +0200160 AllowValues allowValues = flagPolicy.policy.getAllowValues();
ccalvarin334d2f12017-10-05 16:39:42 +0200161 FilterValueOperation.AllowValueOperation allowValueOperation =
162 new FilterValueOperation.AllowValueOperation(loglevel);
163 allowValueOperation.apply(
Alex Humeskyc0d27692016-02-03 00:52:04 +0000164 parser,
ccalvarin7cd9e882017-10-16 22:18:32 +0200165 flagPolicy.origin,
Alex Humeskyc0d27692016-02-03 00:52:04 +0000166 allowValues.getAllowedValuesList(),
Alex Humeskyb1f55c82016-10-25 01:06:39 +0000167 allowValues.hasNewValue() ? allowValues.getNewValue() : null,
168 allowValues.hasUseDefault(),
Alex Humeskyc0d27692016-02-03 00:52:04 +0000169 valueDescription,
ccalvarinca744822017-10-10 13:50:15 +0200170 flagPolicy.description);
Alex Humesky2f3f4cf2015-09-29 01:42:00 +0000171 break;
172
173 case DISALLOW_VALUES:
ccalvarinca744822017-10-10 13:50:15 +0200174 DisallowValues disallowValues = flagPolicy.policy.getDisallowValues();
ccalvarin334d2f12017-10-05 16:39:42 +0200175 FilterValueOperation.DisallowValueOperation disallowValueOperation =
176 new FilterValueOperation.DisallowValueOperation(loglevel);
177 disallowValueOperation.apply(
Alex Humeskyc0d27692016-02-03 00:52:04 +0000178 parser,
ccalvarin7cd9e882017-10-16 22:18:32 +0200179 flagPolicy.origin,
Alex Humeskyc0d27692016-02-03 00:52:04 +0000180 disallowValues.getDisallowedValuesList(),
Alex Humeskyb1f55c82016-10-25 01:06:39 +0000181 disallowValues.hasNewValue() ? disallowValues.getNewValue() : null,
182 disallowValues.hasUseDefault(),
Alex Humeskyc0d27692016-02-03 00:52:04 +0000183 valueDescription,
ccalvarinca744822017-10-10 13:50:15 +0200184 flagPolicy.description);
Alex Humesky2f3f4cf2015-09-29 01:42:00 +0000185 break;
186
187 case OPERATION_NOT_SET:
ccalvarin706bafe2017-03-30 17:34:04 +0000188 throw new PolicyOperationNotSetException(flagName);
Alex Humesky2f3f4cf2015-09-29 01:42:00 +0000189
190 default:
janakrc9560212020-05-27 15:07:36 -0700191 logger.atWarning().log(
192 "Unknown operation '%s' from invocation policy for flag '%s'",
193 flagPolicy.policy.getOperationCase(), flagName);
Alex Humesky2f3f4cf2015-09-29 01:42:00 +0000194 break;
195 }
196 }
197 }
198
ccalvarin706bafe2017-03-30 17:34:04 +0000199 private static class PolicyOperationNotSetException extends OptionsParsingException {
200 PolicyOperationNotSetException(String flagName) {
201 super(String.format("Flag policy for flag '%s' does not " + "have an operation", flagName));
202 }
203 }
204
ccalvarinefc9b632017-05-03 19:05:22 +0200205 private static boolean policyApplies(FlagPolicy policy, ImmutableSet<String> applicableCommands) {
206 // Skip the flag policy if it doesn't apply to this command. If the commands list is empty,
207 // then the policy applies to all commands.
208 if (policy.getCommandsList().isEmpty() || applicableCommands.isEmpty()) {
209 return true;
210 }
211
212 return !Collections.disjoint(policy.getCommandsList(), applicableCommands);
213 }
214
ccalvarinca744822017-10-10 13:50:15 +0200215 /** Returns the expanded and filtered policy that would be enforced for the given command. */
216 public static InvocationPolicy getEffectiveInvocationPolicy(
217 InvocationPolicy invocationPolicy, OptionsParser parser, String command, Level loglevel)
218 throws OptionsParsingException {
219 ImmutableList<FlagPolicyWithContext> effectivePolicies =
220 getEffectivePolicies(invocationPolicy, parser, command, loglevel);
221
222 InvocationPolicy.Builder builder = InvocationPolicy.newBuilder();
223 for (FlagPolicyWithContext policyWithContext : effectivePolicies) {
224 builder.addFlagPolicies(policyWithContext.policy);
225 }
226 return builder.build();
227 }
228
ccalvarin706bafe2017-03-30 17:34:04 +0000229 /**
230 * Takes the provided policy and processes it to the form that can be used on the user options.
231 *
232 * <p>Expands any policies on expansion flags.
233 */
ccalvarinca744822017-10-10 13:50:15 +0200234 private static ImmutableList<FlagPolicyWithContext> getEffectivePolicies(
ccalvarin334d2f12017-10-05 16:39:42 +0200235 InvocationPolicy invocationPolicy, OptionsParser parser, String command, Level loglevel)
ccalvarinefc9b632017-05-03 19:05:22 +0200236 throws OptionsParsingException {
ccalvarin706bafe2017-03-30 17:34:04 +0000237 if (invocationPolicy == null) {
238 return ImmutableList.of();
239 }
240
ccalvarinefc9b632017-05-03 19:05:22 +0200241 ImmutableSet<String> commandAndParentCommands =
242 command == null
Jonathan Bluett-Duncan0df3ddbd2017-08-09 11:13:54 +0200243 ? ImmutableSet.of()
ccalvarinefc9b632017-05-03 19:05:22 +0200244 : CommandNameCache.CommandNameCacheInstance.INSTANCE.get(command);
245
ccalvarin706bafe2017-03-30 17:34:04 +0000246 // Expand all policies to transfer policies on expansion flags to policies on the child flags.
ccalvarinca744822017-10-10 13:50:15 +0200247 List<FlagPolicyWithContext> expandedPolicies = new ArrayList<>();
ccalvarin7cd9e882017-10-16 22:18:32 +0200248 OptionPriority nextPriority =
249 OptionPriority.lowestOptionPriorityAtCategory(PriorityCategory.INVOCATION_POLICY);
ccalvarin706bafe2017-03-30 17:34:04 +0000250 for (FlagPolicy policy : invocationPolicy.getFlagPoliciesList()) {
ccalvarin9c8c7752018-04-17 07:48:38 -0700251 // Explicitly disallow --config in invocation policy.
252 if (policy.getFlagName().equals("config")) {
253 throw new OptionsParsingException(
254 "Invocation policy is applied after --config expansion, changing config values now "
255 + "would have no effect and is disallowed to prevent confusion. Please remove the "
256 + "following policy : "
257 + policy);
258 }
259
ccalvarin7cd9e882017-10-16 22:18:32 +0200260 // These policies are high-level, before expansion, and so are not the implicitDependents or
261 // expansions of any other flag, other than in an obtuse sense from --invocation_policy.
262 OptionPriority currentPriority = nextPriority;
263 OptionInstanceOrigin origin =
264 new OptionInstanceOrigin(currentPriority, INVOCATION_POLICY_SOURCE, null, null);
265 nextPriority = OptionPriority.nextOptionPriority(currentPriority);
ccalvarinefc9b632017-05-03 19:05:22 +0200266 if (!policyApplies(policy, commandAndParentCommands)) {
267 // Only keep and expand policies that are applicable to the current command.
268 continue;
269 }
ccalvarin7cd9e882017-10-16 22:18:32 +0200270
ccalvarin34a9fea2017-10-17 23:27:19 +0200271 OptionDescription optionDescription = parser.getOptionDescription(policy.getFlagName());
ccalvarinca744822017-10-10 13:50:15 +0200272 if (optionDescription == null) {
273 // InvocationPolicy ignores policy on non-existing flags by design, for version
274 // compatibility.
janakrc9560212020-05-27 15:07:36 -0700275 logger.at(loglevel).log(
276 "Flag '%s' specified by invocation policy does not exist, and will be ignored",
277 policy.getFlagName());
ccalvarinca744822017-10-10 13:50:15 +0200278 continue;
279 }
280 FlagPolicyWithContext policyWithContext =
ccalvarin7cd9e882017-10-16 22:18:32 +0200281 new FlagPolicyWithContext(policy, optionDescription, origin);
ccalvarin34a9fea2017-10-17 23:27:19 +0200282 List<FlagPolicyWithContext> policies = expandPolicy(policyWithContext, parser, loglevel);
ccalvarin706bafe2017-03-30 17:34:04 +0000283 expandedPolicies.addAll(policies);
284 }
285
ccalvarinefc9b632017-05-03 19:05:22 +0200286 // Only keep that last policy for each flag.
ccalvarinca744822017-10-10 13:50:15 +0200287 Map<String, FlagPolicyWithContext> effectivePolicy = new HashMap<>();
288 for (FlagPolicyWithContext expandedPolicy : expandedPolicies) {
289 String flagName = expandedPolicy.policy.getFlagName();
ccalvarinefc9b632017-05-03 19:05:22 +0200290 effectivePolicy.put(flagName, expandedPolicy);
291 }
292
Googler7c7255e2017-06-27 20:05:20 +0200293 return ImmutableList.copyOf(effectivePolicy.values());
294 }
295
296 private static void throwAllowValuesOnExpansionFlagException(String flagName)
297 throws OptionsParsingException {
298 throw new OptionsParsingException(
299 String.format("Allow_Values on expansion flags like %s is not allowed.", flagName));
300 }
301
302 private static void throwDisallowValuesOnExpansionFlagException(String flagName)
303 throws OptionsParsingException {
304 throw new OptionsParsingException(
305 String.format("Disallow_Values on expansion flags like %s is not allowed.", flagName));
306 }
307
ajurkowskiec66ded2021-10-27 10:39:52 -0700308 private static OptionsParsingException throwUndefinedBehaviorException(FlagPolicy policy)
309 throws OptionsParsingException {
310 throw new OptionsParsingException(
311 String.format(
312 "SetValue operation from invocation policy for has an undefined behavior: %s", policy));
313 }
314
ccalvarin706bafe2017-03-30 17:34:04 +0000315 /**
316 * Expand a single policy. If the policy is not about an expansion flag, this will simply return a
317 * list with a single element, oneself. If the policy is for an expansion flag, the policy will
318 * get split into multiple policies applying to each flag the original flag expands to.
319 *
320 * <p>None of the flagPolicies returned should be on expansion flags.
321 */
ajurkowski69770672021-06-04 04:43:28 -0700322 private static ImmutableList<FlagPolicyWithContext> expandPolicy(
ccalvarin06e68742018-03-01 07:29:45 -0800323 FlagPolicyWithContext originalPolicy, OptionsParser parser, Level loglevel)
ccalvarin706bafe2017-03-30 17:34:04 +0000324 throws OptionsParsingException {
ajurkowski69770672021-06-04 04:43:28 -0700325 ImmutableList.Builder<FlagPolicyWithContext> expandedPolicies = ImmutableList.builder();
ccalvarin706bafe2017-03-30 17:34:04 +0000326
ccalvarin34a9fea2017-10-17 23:27:19 +0200327 boolean isExpansion = originalPolicy.description.isExpansion();
328 ImmutableList<ParsedOptionDescription> subflags =
329 parser.getExpansionValueDescriptions(
330 originalPolicy.description.getOptionDefinition(), originalPolicy.origin);
331
332 // If we have nothing to expand to, no need to do any further work.
333 if (subflags.isEmpty()) {
ccalvarin7cd9e882017-10-16 22:18:32 +0200334 return ImmutableList.of(originalPolicy);
335 }
ccalvarin706bafe2017-03-30 17:34:04 +0000336
janakrc9560212020-05-27 15:07:36 -0700337 // Log the expansion. This is only really useful for understanding the invocation policy itself.
338 logger.at(loglevel).log(
339 "Expanding %s on option %s to its %s: %s.",
340 originalPolicy.policy.getOperationCase(),
341 originalPolicy.policy.getFlagName(),
342 isExpansion ? "expansions" : "implied flags",
343 LazyArgs.lazy(
344 () ->
345 subflags.stream()
346 .map(f -> "--" + f.getOptionDefinition().getOptionName())
347 .collect(joining("; "))));
ccalvarin36109fc2017-04-05 15:18:51 +0000348
ccalvarinae1d0de2017-04-15 05:19:09 +0200349 // Repeated flags are special, and could set multiple times in an expansion, with the user
350 // expecting both values to be valid. Collect these separately.
ccalvarinca744822017-10-10 13:50:15 +0200351 Multimap<OptionDescription, ParsedOptionDescription> repeatableSubflagsInSetValues =
ccalvarinae1d0de2017-04-15 05:19:09 +0200352 ArrayListMultimap.create();
353
ccalvarin706bafe2017-03-30 17:34:04 +0000354 // Create a flag policy for the child that looks like the parent's policy "transferred" to its
355 // child. Note that this only makes sense for SetValue, when setting an expansion flag, or
356 // UseDefault, when preventing it from being set.
ccalvarinfb153cd2017-09-15 19:29:50 +0200357 for (ParsedOptionDescription currentSubflag : subflags) {
ccalvarinca744822017-10-10 13:50:15 +0200358 OptionDescription subflagOptionDescription =
ccalvarin34a9fea2017-10-17 23:27:19 +0200359 parser.getOptionDescription(currentSubflag.getOptionDefinition().getOptionName());
ccalvarinca744822017-10-10 13:50:15 +0200360
ccalvarin1dce0972017-09-11 20:03:02 +0200361 if (currentSubflag.getOptionDefinition().allowsMultiple()
ccalvarinca744822017-10-10 13:50:15 +0200362 && originalPolicy.policy.getOperationCase().equals(OperationCase.SET_VALUE)) {
363 repeatableSubflagsInSetValues.put(subflagOptionDescription, currentSubflag);
ccalvarinae1d0de2017-04-15 05:19:09 +0200364 } else {
ccalvarinca744822017-10-10 13:50:15 +0200365 FlagPolicyWithContext subflagAsPolicy =
366 getSingleValueSubflagAsPolicy(
367 subflagOptionDescription, currentSubflag, originalPolicy, isExpansion);
ccalvarinae1d0de2017-04-15 05:19:09 +0200368 // In case any of the expanded flags are themselves expansions, recurse.
ccalvarin34a9fea2017-10-17 23:27:19 +0200369 expandedPolicies.addAll(expandPolicy(subflagAsPolicy, parser, loglevel));
ccalvarin706bafe2017-03-30 17:34:04 +0000370 }
ccalvarin706bafe2017-03-30 17:34:04 +0000371 }
372
ccalvarinae1d0de2017-04-15 05:19:09 +0200373 // If there are any repeatable flag SetValues, deal with them together now.
374 // Note that expansion flags have no value, and so cannot have multiple values either.
375 // Skipping the recursion above is fine.
ccalvarinca744822017-10-10 13:50:15 +0200376 for (OptionDescription repeatableFlag : repeatableSubflagsInSetValues.keySet()) {
ccalvarinae1d0de2017-04-15 05:19:09 +0200377 int numValues = repeatableSubflagsInSetValues.get(repeatableFlag).size();
378 ArrayList<String> newValues = new ArrayList<>(numValues);
ccalvarin34a9fea2017-10-17 23:27:19 +0200379 ArrayList<OptionInstanceOrigin> origins = new ArrayList<>(numValues);
ccalvarinfb153cd2017-09-15 19:29:50 +0200380 for (ParsedOptionDescription setValue : repeatableSubflagsInSetValues.get(repeatableFlag)) {
ccalvarina8c0c8d2017-09-14 16:54:39 +0200381 newValues.add(setValue.getUnconvertedValue());
ccalvarin34a9fea2017-10-17 23:27:19 +0200382 origins.add(setValue.getOrigin());
ccalvarinae1d0de2017-04-15 05:19:09 +0200383 }
ccalvarin34a9fea2017-10-17 23:27:19 +0200384 // These options come from expanding a single policy, so they have effectively the same
385 // priority. They could have come from different expansions or implicit requirements in the
386 // recursive resolving of the option list, so just pick the first one. Do collapse the source
387 // strings though, in case there are different sources.
388 OptionInstanceOrigin arbitraryFirstOptionOrigin = origins.get(0);
389 OptionInstanceOrigin originOfSubflags =
390 new OptionInstanceOrigin(
391 arbitraryFirstOptionOrigin.getPriority(),
janakrc9560212020-05-27 15:07:36 -0700392 origins.stream()
ccalvarin34a9fea2017-10-17 23:27:19 +0200393 .map(OptionInstanceOrigin::getSource)
394 .distinct()
janakrc9560212020-05-27 15:07:36 -0700395 .collect(joining(", ")),
ccalvarin34a9fea2017-10-17 23:27:19 +0200396 arbitraryFirstOptionOrigin.getImplicitDependent(),
397 arbitraryFirstOptionOrigin.getExpandedFrom());
ccalvarin7cd9e882017-10-16 22:18:32 +0200398 expandedPolicies.add(
399 getSetValueSubflagAsPolicy(repeatableFlag, newValues, originOfSubflags, originalPolicy));
ccalvarinae1d0de2017-04-15 05:19:09 +0200400 }
401
402 // Don't add the original policy if it was an expansion flag, which have no value, but do add
403 // it if there was either no expansion or if it was a valued flag with implicit requirements.
ccalvarin706bafe2017-03-30 17:34:04 +0000404 if (!isExpansion) {
Googler7c7255e2017-06-27 20:05:20 +0200405 expandedPolicies.add(originalPolicy);
ccalvarin706bafe2017-03-30 17:34:04 +0000406 }
407
ajurkowski69770672021-06-04 04:43:28 -0700408 return expandedPolicies.build();
ccalvarin706bafe2017-03-30 17:34:04 +0000409 }
410
ccalvarinae1d0de2017-04-15 05:19:09 +0200411 /**
412 * Expand a SetValue flag policy on a repeatable flag. SetValue operations are the only flag
413 * policies that set the flag, and so interact with repeatable flags, flags that can be set
414 * multiple times, in subtle ways.
415 *
ccalvarinca744822017-10-10 13:50:15 +0200416 * @param subflagDesc, the description of the flag the SetValue'd expansion flag expands to.
ccalvarinae1d0de2017-04-15 05:19:09 +0200417 * @param subflagValue, the values that the SetValue'd expansion flag expands to for this flag.
ccalvarinae1d0de2017-04-15 05:19:09 +0200418 * @param originalPolicy, the original policy on the expansion flag.
419 * @return the flag policy for the subflag given, this will be part of the expanded form of the
ccalvarin1dce0972017-09-11 20:03:02 +0200420 * SetValue policy on the original flag.
ccalvarinae1d0de2017-04-15 05:19:09 +0200421 */
ccalvarinca744822017-10-10 13:50:15 +0200422 private static FlagPolicyWithContext getSetValueSubflagAsPolicy(
423 OptionDescription subflagDesc,
424 List<String> subflagValue,
ccalvarin7cd9e882017-10-16 22:18:32 +0200425 OptionInstanceOrigin subflagOrigin,
ajurkowskiec66ded2021-10-27 10:39:52 -0700426 FlagPolicyWithContext originalPolicy)
427 throws OptionsParsingException {
Googler199e70c2020-10-08 19:57:27 -0700428 // Some checks.
ccalvarinca744822017-10-10 13:50:15 +0200429 OptionDefinition subflag = subflagDesc.getOptionDefinition();
430 Verify.verify(originalPolicy.policy.getOperationCase().equals(OperationCase.SET_VALUE));
ccalvarin1dce0972017-09-11 20:03:02 +0200431 if (!subflag.allowsMultiple()) {
ccalvarinae1d0de2017-04-15 05:19:09 +0200432 Verify.verify(subflagValue.size() <= 1);
433 }
434
435 // Flag value from the expansion, overridability from the original policy, unless the flag is
436 // repeatable, in which case we care about appendability, not overridability.
437 SetValue.Builder setValueExpansion = SetValue.newBuilder();
438 for (String value : subflagValue) {
439 setValueExpansion.addFlagValue(value);
440 }
ajurkowskiec66ded2021-10-27 10:39:52 -0700441
442 switch (originalPolicy.policy.getSetValue().getBehavior()) {
443 case UNDEFINED:
444 throw throwUndefinedBehaviorException(originalPolicy.policy);
445 case FINAL_VALUE_IGNORE_OVERRIDES:
446 case APPEND:
447 setValueExpansion.setBehavior(Behavior.FINAL_VALUE_IGNORE_OVERRIDES);
448 break;
449 case ALLOW_OVERRIDES:
450 setValueExpansion.setBehavior(
451 subflag.allowsMultiple() ? Behavior.APPEND : Behavior.ALLOW_OVERRIDES);
452 break;
ccalvarinae1d0de2017-04-15 05:19:09 +0200453 }
454
455 // Commands from the original policy, flag name of the expansion
ccalvarinca744822017-10-10 13:50:15 +0200456 return new FlagPolicyWithContext(
457 FlagPolicy.newBuilder()
458 .addAllCommands(originalPolicy.policy.getCommandsList())
459 .setFlagName(subflag.getOptionName())
460 .setSetValue(setValueExpansion)
461 .build(),
ccalvarin7cd9e882017-10-16 22:18:32 +0200462 subflagDesc,
463 subflagOrigin);
ccalvarinae1d0de2017-04-15 05:19:09 +0200464 }
465
466 /**
467 * For an expansion flag in an invocation policy, each flag it expands to must be given a
468 * corresponding policy.
469 */
ccalvarinca744822017-10-10 13:50:15 +0200470 private static FlagPolicyWithContext getSingleValueSubflagAsPolicy(
471 OptionDescription subflagContext,
472 ParsedOptionDescription currentSubflag,
473 FlagPolicyWithContext originalPolicy,
474 boolean isExpansion)
Googler7c7255e2017-06-27 20:05:20 +0200475 throws OptionsParsingException {
ccalvarinca744822017-10-10 13:50:15 +0200476 FlagPolicyWithContext subflagAsPolicy = null;
477 switch (originalPolicy.policy.getOperationCase()) {
ccalvarinae1d0de2017-04-15 05:19:09 +0200478 case SET_VALUE:
ccalvarin1dce0972017-09-11 20:03:02 +0200479 if (currentSubflag.getOptionDefinition().allowsMultiple()) {
ccalvarinc331f242017-08-03 02:12:48 +0200480 throw new AssertionError(
481 "SetValue subflags with allowMultiple should have been dealt with separately and "
482 + "accumulated into a single FlagPolicy.");
483 }
484 // Accept null originalValueStrings, they are expected when the subflag is also an expansion
485 // flag.
486 List<String> subflagValue;
ccalvarina8c0c8d2017-09-14 16:54:39 +0200487 if (currentSubflag.getUnconvertedValue() == null) {
ccalvarinc331f242017-08-03 02:12:48 +0200488 subflagValue = ImmutableList.of();
489 } else {
ccalvarina8c0c8d2017-09-14 16:54:39 +0200490 subflagValue = ImmutableList.of(currentSubflag.getUnconvertedValue());
ccalvarinc331f242017-08-03 02:12:48 +0200491 }
ccalvarin7cd9e882017-10-16 22:18:32 +0200492 subflagAsPolicy =
493 getSetValueSubflagAsPolicy(
494 subflagContext, subflagValue, currentSubflag.getOrigin(), originalPolicy);
ccalvarinae1d0de2017-04-15 05:19:09 +0200495 break;
496
497 case USE_DEFAULT:
498 // Commands from the original policy, flag name of the expansion
499 subflagAsPolicy =
ccalvarinca744822017-10-10 13:50:15 +0200500 new FlagPolicyWithContext(
501 FlagPolicy.newBuilder()
502 .addAllCommands(originalPolicy.policy.getCommandsList())
503 .setFlagName(currentSubflag.getOptionDefinition().getOptionName())
504 .setUseDefault(UseDefault.getDefaultInstance())
505 .build(),
ccalvarin7cd9e882017-10-16 22:18:32 +0200506 subflagContext,
507 currentSubflag.getOrigin());
ccalvarinae1d0de2017-04-15 05:19:09 +0200508 break;
509
510 case ALLOW_VALUES:
511 if (isExpansion) {
ccalvarinca744822017-10-10 13:50:15 +0200512 throwAllowValuesOnExpansionFlagException(originalPolicy.policy.getFlagName());
ccalvarinae1d0de2017-04-15 05:19:09 +0200513 }
514 // If this flag is an implicitRequirement, and some values for the parent flag are
515 // allowed, nothing needs to happen on the implicitRequirement that is set for all
516 // values of the flag.
517 break;
518
519 case DISALLOW_VALUES:
520 if (isExpansion) {
ccalvarinca744822017-10-10 13:50:15 +0200521 throwDisallowValuesOnExpansionFlagException(originalPolicy.policy.getFlagName());
ccalvarinae1d0de2017-04-15 05:19:09 +0200522 }
523 // If this flag is an implicitRequirement, and some values for the parent flag are
524 // disallowed, that implies that all others are allowed, so nothing needs to happen
525 // on the implicitRequirement that is set for all values of the parent flag.
526 break;
527
528 case OPERATION_NOT_SET:
ccalvarinca744822017-10-10 13:50:15 +0200529 throw new PolicyOperationNotSetException(originalPolicy.policy.getFlagName());
ccalvarinae1d0de2017-04-15 05:19:09 +0200530
531 default:
532 return null;
533 }
534 return subflagAsPolicy;
535 }
536
Alex Humesky2f3f4cf2015-09-29 01:42:00 +0000537 private static void applySetValueOperation(
538 OptionsParser parser,
ccalvarinca744822017-10-10 13:50:15 +0200539 FlagPolicyWithContext flagPolicy,
Alex Humesky2f3f4cf2015-09-29 01:42:00 +0000540 OptionValueDescription valueDescription,
ccalvarin334d2f12017-10-05 16:39:42 +0200541 Level loglevel)
Janak Ramakrishnanb92c0972016-03-23 16:47:13 +0000542 throws OptionsParsingException {
ccalvarinca744822017-10-10 13:50:15 +0200543 SetValue setValue = flagPolicy.policy.getSetValue();
544 OptionDefinition optionDefinition = flagPolicy.description.getOptionDefinition();
Alex Humesky2f3f4cf2015-09-29 01:42:00 +0000545
546 // SetValue.flag_value must have at least 1 value.
547 if (setValue.getFlagValueCount() == 0) {
Janak Ramakrishnanb92c0972016-03-23 16:47:13 +0000548 throw new OptionsParsingException(
549 String.format(
ccalvarin7cd9e882017-10-16 22:18:32 +0200550 "SetValue operation from invocation policy for %s does not have a value",
551 optionDefinition));
Alex Humesky2f3f4cf2015-09-29 01:42:00 +0000552 }
Luis Fernando Pino Duqueb1b28b62016-02-25 14:25:19 +0000553
Alex Humesky2f3f4cf2015-09-29 01:42:00 +0000554 // Flag must allow multiple values if multiple values are specified by the policy.
ccalvarin00443492017-08-30 00:23:40 +0200555 if (setValue.getFlagValueCount() > 1
ccalvarinca744822017-10-10 13:50:15 +0200556 && !flagPolicy.description.getOptionDefinition().allowsMultiple()) {
Janak Ramakrishnanb92c0972016-03-23 16:47:13 +0000557 throw new OptionsParsingException(
558 String.format(
ccalvarin7cd9e882017-10-16 22:18:32 +0200559 "SetValue operation from invocation policy sets multiple values for %s which "
Janak Ramakrishnanb92c0972016-03-23 16:47:13 +0000560 + "does not allow multiple values",
ccalvarin7cd9e882017-10-16 22:18:32 +0200561 optionDefinition));
Alex Humesky2f3f4cf2015-09-29 01:42:00 +0000562 }
Luis Fernando Pino Duqueb1b28b62016-02-25 14:25:19 +0000563
ajurkowskiec66ded2021-10-27 10:39:52 -0700564 switch (setValue.getBehavior()) {
565 case UNDEFINED:
566 throw throwUndefinedBehaviorException(flagPolicy.policy);
567 case ALLOW_OVERRIDES:
568 if (valueDescription != null) {
569 // The user set the value for the flag but the flag policy is overridable, so keep the
570 // user's
571 // value.
572 logger.at(loglevel).log(
573 "Keeping value '%s' from source '%s' for %s because the invocation policy specifying "
574 + "the value(s) '%s' is overridable",
575 valueDescription.getValue(),
576 valueDescription.getSourceString(),
577 optionDefinition,
578 setValue.getFlagValueList());
579 // Nothing to do -- the value already has an override.
580 return;
581 }
582 break;
583 case FINAL_VALUE_IGNORE_OVERRIDES:
Alex Humesky05950072016-05-09 18:38:55 +0000584 // Clear the value in case the flag is a repeated flag so that values don't accumulate.
ccalvarinca744822017-10-10 13:50:15 +0200585 parser.clearValue(flagPolicy.description.getOptionDefinition());
ajurkowskiec66ded2021-10-27 10:39:52 -0700586 break;
587 case APPEND:
588 break;
589 }
ccalvarin7cd9e882017-10-16 22:18:32 +0200590
ajurkowskiec66ded2021-10-27 10:39:52 -0700591 // Set all the flag values from the policy.
592 for (String flagValue : setValue.getFlagValueList()) {
593 if (valueDescription == null) {
594 logger.at(loglevel).log(
595 "Setting value for %s from invocation policy to '%s', overriding the default value "
596 + "'%s'",
597 optionDefinition, flagValue, optionDefinition.getDefaultValue());
598 } else {
599 logger.at(loglevel).log(
600 "Setting value for %s from invocation policy to '%s', overriding value '%s' from '%s'",
601 optionDefinition,
602 flagValue,
603 valueDescription.getValue(),
604 valueDescription.getSourceString());
605 }
Googler4442bb92021-10-15 09:15:28 -0700606
ajurkowskie88245d2021-06-10 18:00:38 -0700607 parser.setOptionValueAtSpecificPriorityWithoutExpansion(
608 flagPolicy.origin, optionDefinition, flagValue);
Alex Humesky2f3f4cf2015-09-29 01:42:00 +0000609 }
610 }
611
Alex Humeskyb1f55c82016-10-25 01:06:39 +0000612 private static void applyUseDefaultOperation(
ccalvarin334d2f12017-10-05 16:39:42 +0200613 OptionsParser parser, String policyType, OptionDefinition option, Level loglevel)
ccalvarin1dce0972017-09-11 20:03:02 +0200614 throws OptionsParsingException {
615 OptionValueDescription clearedValueDescription = parser.clearValue(option);
ccalvarin0e02f532017-04-04 18:31:27 +0000616 if (clearedValueDescription != null) {
617 // Log the removed value.
ccalvarin1dce0972017-09-11 20:03:02 +0200618 String clearedFlagName = clearedValueDescription.getOptionDefinition().getOptionName();
ccalvarinca744822017-10-10 13:50:15 +0200619 Object clearedFlagDefaultValue =
620 clearedValueDescription.getOptionDefinition().getDefaultValue();
janakrc9560212020-05-27 15:07:36 -0700621 logger.at(loglevel).log(
622 "Using default value '%s' for flag '%s' as specified by %s invocation policy, "
623 + "overriding original value '%s' from '%s'",
624 clearedFlagDefaultValue,
625 clearedFlagName,
626 policyType,
627 clearedValueDescription.getValue(),
628 clearedValueDescription.getSourceString());
Alex Humesky2f3f4cf2015-09-29 01:42:00 +0000629 }
630 }
631
ccalvarin334d2f12017-10-05 16:39:42 +0200632 /** Checks the user's flag values against a filtering function. */
Alex Humeskyc0d27692016-02-03 00:52:04 +0000633 private abstract static class FilterValueOperation {
Alex Humesky2f3f4cf2015-09-29 01:42:00 +0000634
ccalvarin334d2f12017-10-05 16:39:42 +0200635 private static final class AllowValueOperation extends FilterValueOperation {
636 AllowValueOperation(Level loglevel) {
637 super("Allow", loglevel);
638 }
Alex Humeskyc0d27692016-02-03 00:52:04 +0000639
ccalvarin334d2f12017-10-05 16:39:42 +0200640 @Override
641 boolean isFlagValueAllowed(Set<Object> convertedPolicyValues, Object value) {
642 return convertedPolicyValues.contains(value);
643 }
644 }
645
646 private static final class DisallowValueOperation extends FilterValueOperation {
647 DisallowValueOperation(Level loglevel) {
648 super("Disalllow", loglevel);
649 }
650
651 @Override
652 boolean isFlagValueAllowed(Set<Object> convertedPolicyValues, Object value) {
653 // In a disallow operation, the values that the flag policy specifies are not allowed,
654 // so the value is allowed if the set of policy values does not contain the current
655 // flag value.
656 return !convertedPolicyValues.contains(value);
657 }
658 }
Luis Fernando Pino Duqueb1b28b62016-02-25 14:25:19 +0000659
Alex Humeskyc0d27692016-02-03 00:52:04 +0000660 private final String policyType;
ccalvarin334d2f12017-10-05 16:39:42 +0200661 private final Level loglevel;
Alex Humeskyc0d27692016-02-03 00:52:04 +0000662
ccalvarin334d2f12017-10-05 16:39:42 +0200663 FilterValueOperation(String policyType, Level loglevel) {
Alex Humeskyc0d27692016-02-03 00:52:04 +0000664 this.policyType = policyType;
ccalvarin334d2f12017-10-05 16:39:42 +0200665 this.loglevel = loglevel;
Alex Humesky2f3f4cf2015-09-29 01:42:00 +0000666 }
667
Alex Humeskyc0d27692016-02-03 00:52:04 +0000668 /**
669 * Determines if the given value is allowed.
670 *
671 * @param convertedPolicyValues The values given from the FlagPolicy, converted to real objects.
672 * @param value The user value of the flag.
673 * @return True if the value should be allowed, false if it should not.
674 */
Alex Humeskyb1f55c82016-10-25 01:06:39 +0000675 abstract boolean isFlagValueAllowed(Set<Object> convertedPolicyValues, Object value);
Luis Fernando Pino Duqueb1b28b62016-02-25 14:25:19 +0000676
Alex Humeskyc0d27692016-02-03 00:52:04 +0000677 void apply(
678 OptionsParser parser,
ccalvarin7cd9e882017-10-16 22:18:32 +0200679 OptionInstanceOrigin origin,
Alex Humeskyc0d27692016-02-03 00:52:04 +0000680 List<String> policyValues,
Alex Humeskyb1f55c82016-10-25 01:06:39 +0000681 String newValue,
682 boolean useDefault,
Alex Humeskyc0d27692016-02-03 00:52:04 +0000683 OptionValueDescription valueDescription,
Janak Ramakrishnanb92c0972016-03-23 16:47:13 +0000684 OptionDescription optionDescription)
685 throws OptionsParsingException {
ccalvarin1dce0972017-09-11 20:03:02 +0200686 OptionDefinition optionDefinition = optionDescription.getOptionDefinition();
Alex Humeskyc0d27692016-02-03 00:52:04 +0000687 // Convert all the allowed values from strings to real objects using the options'
688 // converters so that they can be checked for equality using real .equals() instead
689 // of string comparison. For example, "--foo=0", "--foo=false", "--nofoo", and "-f-"
690 // (if the option has an abbreviation) are all equal for boolean flags. Plus converters
691 // can be arbitrarily complex.
Jonathan Bluett-Duncan0df3ddbd2017-08-09 11:13:54 +0200692 Set<Object> convertedPolicyValues = new HashSet<>();
Alex Humeskyc0d27692016-02-03 00:52:04 +0000693 for (String value : policyValues) {
ccalvarin1dce0972017-09-11 20:03:02 +0200694 Object convertedValue = optionDefinition.getConverter().convert(value);
Alex Humesky64155872016-12-16 22:12:52 +0000695 // Some converters return lists, and if the flag is a repeatable flag, the items in the
696 // list from the converter should be added, and not the list itself. Otherwise the items
697 // from invocation policy will be compared to lists, which will never work.
698 // See OptionsParserImpl.ParsedOptionEntry.addValue.
ccalvarin1dce0972017-09-11 20:03:02 +0200699 if (optionDefinition.allowsMultiple() && convertedValue instanceof List<?>) {
Alex Humesky64155872016-12-16 22:12:52 +0000700 convertedPolicyValues.addAll((List<?>) convertedValue);
701 } else {
ccalvarin1dce0972017-09-11 20:03:02 +0200702 convertedPolicyValues.add(optionDefinition.getConverter().convert(value));
Alex Humesky64155872016-12-16 22:12:52 +0000703 }
Alex Humeskyc0d27692016-02-03 00:52:04 +0000704 }
705
Alex Humeskyb1f55c82016-10-25 01:06:39 +0000706 // Check that if the default value of the flag is disallowed by the policy, that the policy
ccalvarin06e68742018-03-01 07:29:45 -0800707 // does not also set use_default. Otherwise the default value would still be set if the
Alex Humeskyb1f55c82016-10-25 01:06:39 +0000708 // user uses a disallowed value. This doesn't apply to repeatable flags since the default
ccalvarin06e68742018-03-01 07:29:45 -0800709 // value for repeatable flags is always the empty list. It also doesn't apply to flags that
710 // are null by default, since these flags' default value is not parsed by the converter, so
711 // there is no guarantee that there exists an accepted user-input value that would also set
712 // the value to NULL. In these cases, we assume that "unset" is a distinct value that is
713 // always allowed.
714 if (!optionDescription.getOptionDefinition().allowsMultiple()
715 && !optionDescription.getOptionDefinition().isSpecialNullDefault()) {
Alex Humeskyb1f55c82016-10-25 01:06:39 +0000716 boolean defaultValueAllowed =
ccalvarin00443492017-08-30 00:23:40 +0200717 isFlagValueAllowed(
718 convertedPolicyValues, optionDescription.getOptionDefinition().getDefaultValue());
Alex Humeskyb1f55c82016-10-25 01:06:39 +0000719 if (!defaultValueAllowed && useDefault) {
720 throw new OptionsParsingException(
721 String.format(
ccalvarin7cd9e882017-10-16 22:18:32 +0200722 "%sValues policy disallows the default value '%s' for %s but also specifies to "
723 + "use the default value",
724 policyType, optionDefinition.getDefaultValue(), optionDefinition));
Alex Humeskyb1f55c82016-10-25 01:06:39 +0000725 }
726 }
727
Alex Humeskyc0d27692016-02-03 00:52:04 +0000728 if (valueDescription == null) {
729 // Nothing has set the value yet, so check that the default value from the flag's
730 // definition is allowed. The else case below (i.e. valueDescription is not null) checks for
731 // the flag allowing multiple values, however, flags that allow multiple values cannot have
732 // default values, and their value is always the empty list if they haven't been specified,
733 // which is why new_default_value is not a repeated field.
ccalvarin7cd9e882017-10-16 22:18:32 +0200734 checkDefaultValue(
735 parser, origin, optionDescription, policyValues, newValue, convertedPolicyValues);
Alex Humeskyc0d27692016-02-03 00:52:04 +0000736 } else {
737 checkUserValue(
Alex Humeskyb1f55c82016-10-25 01:06:39 +0000738 parser,
ccalvarin7cd9e882017-10-16 22:18:32 +0200739 origin,
ccalvarin1dce0972017-09-11 20:03:02 +0200740 optionDescription,
741 valueDescription,
Alex Humeskyb1f55c82016-10-25 01:06:39 +0000742 policyValues,
743 newValue,
744 useDefault,
Alex Humeskyb1f55c82016-10-25 01:06:39 +0000745 convertedPolicyValues);
Alex Humeskyc0d27692016-02-03 00:52:04 +0000746 }
747 }
Luis Fernando Pino Duqueb1b28b62016-02-25 14:25:19 +0000748
Alex Humeskyc0d27692016-02-03 00:52:04 +0000749 void checkDefaultValue(
750 OptionsParser parser,
ccalvarin7cd9e882017-10-16 22:18:32 +0200751 OptionInstanceOrigin origin,
ccalvarin1dce0972017-09-11 20:03:02 +0200752 OptionDescription optionDescription,
Alex Humeskyc0d27692016-02-03 00:52:04 +0000753 List<String> policyValues,
Alex Humeskyb1f55c82016-10-25 01:06:39 +0000754 String newValue,
Janak Ramakrishnanb92c0972016-03-23 16:47:13 +0000755 Set<Object> convertedPolicyValues)
756 throws OptionsParsingException {
Alex Humeskyc0d27692016-02-03 00:52:04 +0000757
ccalvarin1dce0972017-09-11 20:03:02 +0200758 OptionDefinition optionDefinition = optionDescription.getOptionDefinition();
ccalvarin06e68742018-03-01 07:29:45 -0800759 if (optionDefinition.isSpecialNullDefault()) {
760 // Do nothing, the unset value by definition cannot be set. In option filtering operations,
761 // the value is being filtered, but the value that is `no value` passes any filter.
762 // Otherwise, there is no way to "usedefault" on one of these options that has no value by
763 // default.
764 } else if (!isFlagValueAllowed(convertedPolicyValues, optionDefinition.getDefaultValue())) {
Alex Humeskyb1f55c82016-10-25 01:06:39 +0000765 if (newValue != null) {
ccalvarin7cd9e882017-10-16 22:18:32 +0200766 // Use the default value from the policy, since the original default is not allowed
janakrc9560212020-05-27 15:07:36 -0700767 logger.at(loglevel).log(
768 "Overriding default value '%s' for %s with value '%s' specified by invocation "
769 + "policy. %sed values are: %s",
770 optionDefinition.getDefaultValue(),
771 optionDefinition,
772 newValue,
773 policyType,
774 policyValues);
ccalvarin1dce0972017-09-11 20:03:02 +0200775 parser.clearValue(optionDefinition);
ajurkowskie88245d2021-06-10 18:00:38 -0700776 parser.setOptionValueAtSpecificPriorityWithoutExpansion(
777 origin, optionDefinition, newValue);
Alex Humesky2f3f4cf2015-09-29 01:42:00 +0000778 } else {
Alex Humeskyb1f55c82016-10-25 01:06:39 +0000779 // The operation disallows the default value, but doesn't supply a new value.
Janak Ramakrishnanb92c0972016-03-23 16:47:13 +0000780 throw new OptionsParsingException(
781 String.format(
ccalvarin7cd9e882017-10-16 22:18:32 +0200782 "Default flag value '%s' for %s is not allowed by invocation policy, but "
ccalvarin3ab171a2017-09-19 16:36:49 +0200783 + "the policy does not provide a new value. %sed values are: %s",
ccalvarin00443492017-08-30 00:23:40 +0200784 optionDescription.getOptionDefinition().getDefaultValue(),
ccalvarin7cd9e882017-10-16 22:18:32 +0200785 optionDefinition,
Janak Ramakrishnanb92c0972016-03-23 16:47:13 +0000786 policyType,
787 policyValues));
Alex Humesky2f3f4cf2015-09-29 01:42:00 +0000788 }
789 }
Alex Humeskyc0d27692016-02-03 00:52:04 +0000790 }
Luis Fernando Pino Duqueb1b28b62016-02-25 14:25:19 +0000791
Alex Humeskyc0d27692016-02-03 00:52:04 +0000792 void checkUserValue(
Alex Humeskyb1f55c82016-10-25 01:06:39 +0000793 OptionsParser parser,
ccalvarin7cd9e882017-10-16 22:18:32 +0200794 OptionInstanceOrigin origin,
ccalvarin1dce0972017-09-11 20:03:02 +0200795 OptionDescription optionDescription,
796 OptionValueDescription valueDescription,
Alex Humeskyc0d27692016-02-03 00:52:04 +0000797 List<String> policyValues,
Alex Humeskyb1f55c82016-10-25 01:06:39 +0000798 String newValue,
799 boolean useDefault,
Janak Ramakrishnanb92c0972016-03-23 16:47:13 +0000800 Set<Object> convertedPolicyValues)
801 throws OptionsParsingException {
ccalvarin1dce0972017-09-11 20:03:02 +0200802 OptionDefinition option = optionDescription.getOptionDefinition();
ccalvarin00443492017-08-30 00:23:40 +0200803 if (optionDescription.getOptionDefinition().allowsMultiple()) {
Alex Humeskyc0d27692016-02-03 00:52:04 +0000804 // allowMultiple requires that the type of the option be List<T>, so cast from Object
805 // to List<?>.
Alex Humeskyb1f55c82016-10-25 01:06:39 +0000806 List<?> optionValues = (List<?>) valueDescription.getValue();
807 for (Object value : optionValues) {
808 if (!isFlagValueAllowed(convertedPolicyValues, value)) {
809 if (useDefault) {
ccalvarin334d2f12017-10-05 16:39:42 +0200810 applyUseDefaultOperation(parser, policyType + "Values", option, loglevel);
Alex Humeskyb1f55c82016-10-25 01:06:39 +0000811 } else {
812 throw new OptionsParsingException(
813 String.format(
ccalvarin7cd9e882017-10-16 22:18:32 +0200814 "Flag value '%s' for %s is not allowed by invocation policy. %sed values "
815 + "are: %s",
816 value, option, policyType, policyValues));
Alex Humeskyb1f55c82016-10-25 01:06:39 +0000817 }
818 }
819 }
Alex Humesky2f3f4cf2015-09-29 01:42:00 +0000820
Alex Humeskyb1f55c82016-10-25 01:06:39 +0000821 } else {
822
823 if (!isFlagValueAllowed(convertedPolicyValues, valueDescription.getValue())) {
824 if (newValue != null) {
janakrc9560212020-05-27 15:07:36 -0700825 logger.at(loglevel).log(
826 "Overriding disallowed value '%s' for %s with value '%s' "
827 + "specified by invocation policy. %sed values are: %s",
828 valueDescription.getValue(), option, newValue, policyType, policyValues);
ccalvarin1dce0972017-09-11 20:03:02 +0200829 parser.clearValue(option);
ajurkowskie88245d2021-06-10 18:00:38 -0700830 parser.setOptionValueAtSpecificPriorityWithoutExpansion(origin, option, newValue);
Alex Humeskyb1f55c82016-10-25 01:06:39 +0000831 } else if (useDefault) {
ccalvarin334d2f12017-10-05 16:39:42 +0200832 applyUseDefaultOperation(parser, policyType + "Values", option, loglevel);
Alex Humeskyb1f55c82016-10-25 01:06:39 +0000833 } else {
834 throw new OptionsParsingException(
835 String.format(
ccalvarin7cd9e882017-10-16 22:18:32 +0200836 "Flag value '%s' for %s is not allowed by invocation policy and the "
Alex Humeskyb1f55c82016-10-25 01:06:39 +0000837 + "policy does not specify a new value. %sed values are: %s",
ccalvarin7cd9e882017-10-16 22:18:32 +0200838 valueDescription.getValue(), option, policyType, policyValues));
Alex Humeskyb1f55c82016-10-25 01:06:39 +0000839 }
Alex Humesky2f3f4cf2015-09-29 01:42:00 +0000840 }
841 }
842 }
843 }
Alex Humesky2f3f4cf2015-09-29 01:42:00 +0000844}