| // Copyright 2014 The Bazel Authors. All rights reserved. | |
| // | |
| // Licensed under the Apache License, Version 2.0 (the "License"); | |
| // you may not use this file except in compliance with the License. | |
| // You may obtain a copy of the License at | |
| // | |
| // http://www.apache.org/licenses/LICENSE-2.0 | |
| // | |
| // Unless required by applicable law or agreed to in writing, software | |
| // distributed under the License is distributed on an "AS IS" BASIS, | |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| // See the License for the specific language governing permissions and | |
| // limitations under the License. | |
| package com.google.devtools.build.lib.actions; | |
| import static java.util.Objects.requireNonNull; | |
| import com.google.common.base.Function; | |
| import com.google.common.base.Preconditions; | |
| import com.google.common.collect.ImmutableMap; | |
| import com.google.devtools.build.lib.actions.ExecutionRequirements.ParseableRequirement.ValidationException; | |
| import com.google.devtools.build.lib.server.FailureDetails.FailureDetail; | |
| import com.google.devtools.build.lib.server.FailureDetails.TestAction; | |
| import com.google.devtools.build.lib.server.FailureDetails.TestAction.Code; | |
| import java.util.HashMap; | |
| import java.util.Map; | |
| import java.util.regex.Matcher; | |
| import java.util.regex.Pattern; | |
| import javax.annotation.Nullable; | |
| /** | |
| * Strings used to express requirements on action execution environments. | |
| * | |
| * <ol> | |
| * If you are adding a new execution requirement, pay attention to the following: | |
| * <li>If its name starts with one of the supported prefixes, then it can be also used as a tag on | |
| * a target and will be propagated to the execution requirements, see for prefixes {@link | |
| * com.google.devtools.build.lib.packages.TargetUtils#getExecutionInfo} | |
| * <li>If this is a potentially conflicting execution requirements, e.g. you are adding a pair | |
| * 'requires-x' and 'block-x', you MUST take care of a potential conflict in the Executor that | |
| * is using new execution requirements. As an example, see {@link | |
| * Spawns#requiresNetwork(com.google.devtools.build.lib.actions.Spawn, boolean)}. | |
| * </ol> | |
| */ | |
| public class ExecutionRequirements { | |
| /** An execution requirement that can be split into a key and a value part using a regex. */ | |
| public record ParseableRequirement( | |
| String userFriendlyName, Pattern detectionPattern, Function<String, String> validator) { | |
| public ParseableRequirement { | |
| requireNonNull(userFriendlyName, "userFriendlyName"); | |
| requireNonNull(detectionPattern, "detectionPattern"); | |
| requireNonNull(validator, "validator"); | |
| } | |
| /** | |
| * Thrown when a {@link ParseableRequirement} feels responsible for a tag, but the {@link | |
| * #validator()} method returns an error. | |
| */ | |
| public static class ValidationException extends Exception { | |
| private final String tagValue; | |
| /** | |
| * Creates a new {@link ValidationException}. | |
| * | |
| * @param tagValue the erroneous value that was parsed from the tag. | |
| * @param errorMsg an error message that tells the user what's wrong with the value. | |
| */ | |
| public ValidationException(String tagValue, String errorMsg) { | |
| super(errorMsg); | |
| this.tagValue = tagValue; | |
| } | |
| /** | |
| * Returns the erroneous value of the parsed tag. | |
| * | |
| * <p>Useful to put in error messages shown to the user. | |
| */ | |
| public String getTagValue() { | |
| return tagValue; | |
| } | |
| } | |
| /** | |
| * Create a new parseable execution requirement definition. | |
| * | |
| * <p>If a tag doesn't match the detectionPattern, it will be ignored. If a tag matches the | |
| * detectionPattern, but not the validationPattern, it is assumed that the value is somehow | |
| * wrong (e.g. the user put a float or random string where we expected an integer). | |
| * | |
| * @param userFriendlyName a human readable name of the tag and its format, e.g. "cpu:<int>" | |
| * @param detectionPattern a regex that will be used to detect whether a tag matches this | |
| * execution requirement. It should have one capture group that grabs the value of the tag. | |
| * This should be general enough to permit even wrong value types. Example: "cpu:(.+)". | |
| * @param validator a Function that will be used to validate the value of the tag. It should | |
| * return null if the value is fine to use or a human-friendly error message describing why | |
| * the value is not valid. | |
| */ | |
| static ParseableRequirement create( | |
| String userFriendlyName, Pattern detectionPattern, Function<String, String> validator) { | |
| return new ParseableRequirement(userFriendlyName, detectionPattern, validator); | |
| } | |
| /** | |
| * Returns the parsed value from a tag, if this {@link ParseableRequirement} detects that it is | |
| * responsible for it, otherwise returns {@code null}. | |
| * | |
| * @throws ValidationException if the value parsed out of the tag doesn't pass the validator. | |
| */ | |
| @Nullable | |
| public String parseIfMatches(String tag) throws ValidationException { | |
| Matcher matcher = detectionPattern().matcher(tag); | |
| if (!matcher.matches()) { | |
| return null; | |
| } | |
| String tagValue = matcher.group(1); | |
| String errorMsg = validator().apply(tagValue); | |
| if (errorMsg != null) { | |
| throw new ValidationException(tagValue, errorMsg); | |
| } | |
| return tagValue; | |
| } | |
| } | |
| /** If specified, the timeout of this action in seconds. Must be decimal integer. */ | |
| public static final String TIMEOUT = "timeout"; | |
| /** If an action would not successfully run other than on Darwin. */ | |
| public static final String REQUIRES_DARWIN = "requires-darwin"; | |
| /** Whether we should disable prefetching of inputs before running a local action. */ | |
| public static final String DISABLE_LOCAL_PREFETCH = "disable-local-prefetch"; | |
| /** How many hardware threads an action requires for execution. */ | |
| public static final ParseableRequirement CPU = | |
| ParseableRequirement.create( | |
| "cpu:<int>", | |
| Pattern.compile("cpu:(.+)"), | |
| s -> { | |
| Preconditions.checkNotNull(s); | |
| int value; | |
| try { | |
| value = Integer.parseInt(s); | |
| } catch (NumberFormatException e) { | |
| return "can't be parsed as an integer"; | |
| } | |
| // De-and-reserialize & compare to only allow canonical integer formats. | |
| if (!Integer.toString(value).equals(s)) { | |
| return "must be in canonical format (e.g. '4' instead of '+04')"; | |
| } | |
| if (value < 1) { | |
| return "can't be zero or negative"; | |
| } | |
| return null; | |
| }); | |
| /** How many extra resources an action requires for execution. */ | |
| public static final ParseableRequirement RESOURCES = | |
| ParseableRequirement.create( | |
| "resources:<str>:<float>", | |
| Pattern.compile("resources:(.+:.+)"), | |
| s -> { | |
| Preconditions.checkNotNull(s); | |
| int splitIndex = s.indexOf(":"); | |
| String resourceCount = s.substring(splitIndex + 1); | |
| float value; | |
| try { | |
| value = Float.parseFloat(resourceCount); | |
| } catch (NumberFormatException e) { | |
| return "can't be parsed as a float"; | |
| } | |
| if (value < 0) { | |
| return "can't be negative"; | |
| } | |
| return null; | |
| }); | |
| /** | |
| * Parses resource requirements from a string map. Handles both execution info (tag format) and | |
| * exec_properties (key-value format): | |
| * | |
| * <ul> | |
| * <li>Tags: key is the full tag (e.g. {@code "resources:cpu:4"} or {@code "cpu:2"}), value is | |
| * empty | |
| * <li>exec_properties: key is the resource prefix (e.g. {@code "resources:cpu"}), value is the | |
| * amount (e.g. {@code "4"}) | |
| * </ul> | |
| * | |
| * <p>In both cases, the entry is normalized to tag format and parsed with {@link #RESOURCES} and | |
| * {@link #CPU}. | |
| * | |
| * @return resource name to amount mapping; empty if no resource entries are found | |
| * @throws UserExecException if a matching entry has an invalid value or a resource is specified | |
| * more than once | |
| */ | |
| public static ImmutableMap<String, Double> parseResources(Map<String, String> map) | |
| throws UserExecException { | |
| if (map.isEmpty()) { | |
| return ImmutableMap.of(); | |
| } | |
| Map<String, Double> resources = new HashMap<>(); | |
| for (Map.Entry<String, String> entry : map.entrySet()) { | |
| // Normalize to tag format: tags have the value baked into the key (e.g. "resources:cpu:4" | |
| // with empty value), exec_properties split it (e.g. key "resources:cpu", value "4"). | |
| String tag = | |
| entry.getValue().isEmpty() ? entry.getKey() : entry.getKey() + ":" + entry.getValue(); | |
| ParseableRequirement requirement = RESOURCES; | |
| String resource; | |
| String amount; | |
| try { | |
| String parsed = requirement.parseIfMatches(tag); | |
| if (parsed != null) { | |
| int splitIndex = parsed.indexOf(":"); | |
| resource = parsed.substring(0, splitIndex); | |
| amount = parsed.substring(splitIndex + 1); | |
| } else { | |
| requirement = CPU; | |
| String cpuValue = requirement.parseIfMatches(tag); | |
| if (cpuValue == null) { | |
| if (tag.startsWith("resources:")) { | |
| // A key clearly intended as a resource that didn't match the expected format (e.g. | |
| // "resources:cpu:" with an empty amount). | |
| throw new UserExecException( | |
| createFailureDetail( | |
| String.format( | |
| "'%s' is not a valid '%s' entry", tag, RESOURCES.userFriendlyName()), | |
| Code.INVALID_CPU_TAG)); | |
| } | |
| continue; | |
| } | |
| resource = "cpu"; | |
| amount = cpuValue; | |
| } | |
| } catch (ValidationException e) { | |
| throw new UserExecException( | |
| createFailureDetail( | |
| String.format( | |
| "'%s' has a '%s' entry, but its value '%s' didn't pass validation: %s", | |
| tag, requirement.userFriendlyName(), e.getTagValue(), e.getMessage()), | |
| Code.INVALID_CPU_TAG)); | |
| } | |
| if (resources.containsKey(resource)) { | |
| throw new UserExecException( | |
| createFailureDetail( | |
| String.format( | |
| "'%s' has more than one entry for resource '%s', but duplicates aren't" | |
| + " allowed", | |
| tag, resource), | |
| Code.DUPLICATE_CPU_TAGS)); | |
| } | |
| resources.put(resource, Double.parseDouble(amount)); | |
| } | |
| return ImmutableMap.copyOf(resources); | |
| } | |
| private static FailureDetail createFailureDetail(String message, Code detailedCode) { | |
| return FailureDetail.newBuilder() | |
| .setMessage(message) | |
| .setTestAction(TestAction.newBuilder().setCode(detailedCode)) | |
| .build(); | |
| } | |
| /** If an action supports running in persistent worker mode. */ | |
| public static final String SUPPORTS_WORKERS = "supports-workers"; | |
| public static final String SUPPORTS_MULTIPLEX_WORKERS = "supports-multiplex-workers"; | |
| /** Specify the type of worker protocol the worker uses. */ | |
| public static final String REQUIRES_WORKER_PROTOCOL = "requires-worker-protocol"; | |
| public static final String SUPPORTS_WORKER_CANCELLATION = "supports-worker-cancellation"; | |
| public static final String SUPPORTS_MULTIPLEX_SANDBOXING = "supports-multiplex-sandboxing"; | |
| /** Denotes what the type of worker protocol the worker uses. */ | |
| public enum WorkerProtocolFormat { | |
| JSON, | |
| PROTO, | |
| } | |
| /** Override for the action's mnemonic to allow for better worker process reuse. */ | |
| public static final String WORKER_KEY_MNEMONIC = "worker-key-mnemonic"; | |
| public static final ImmutableMap<String, String> WORKER_MODE_ENABLED = | |
| ImmutableMap.of(SUPPORTS_WORKERS, "1"); | |
| public static final ImmutableMap<String, String> WORKER_MULTIPLEX_MODE_ENABLED = | |
| ImmutableMap.of(SUPPORTS_MULTIPLEX_WORKERS, "1"); | |
| /** | |
| * Requires local execution without sandboxing for a spawn. | |
| * | |
| * <p>This tag is deprecated; use no-cache, no-remote, or no-sandbox instead. | |
| */ | |
| public static final String LOCAL = "local"; | |
| /** | |
| * Disables local and remote caching for a spawn, but note that the local action cache may still | |
| * apply. | |
| * | |
| * <p>This tag can also be set on an action, in which case it completely disables all caching for | |
| * that action, but note that action-generated spawns may still be cached, unless they also carry | |
| * this tag. | |
| */ | |
| public static final String NO_CACHE = "no-cache"; | |
| /** Disables remote caching of a spawn. Note: does not disable remote execution */ | |
| public static final String NO_REMOTE_CACHE = "no-remote-cache"; | |
| /** Disables upload part of remote caching of a spawn. Note: does not disable remote execution */ | |
| public static final String NO_REMOTE_CACHE_UPLOAD = "no-remote-cache-upload"; | |
| /** Disables remote execution of a spawn. Note: does not disable remote caching */ | |
| public static final String NO_REMOTE_EXEC = "no-remote-exec"; | |
| /** Tag for Google internal use. Requires local execution with correct permissions. */ | |
| public static final String NO_TESTLOASD = "no-testloasd"; | |
| /** | |
| * Disables both remote execution and remote caching of a spawn. This is the equivalent of using | |
| * no-remote-cache and no-remote-exec together. | |
| */ | |
| public static final String NO_REMOTE = "no-remote"; | |
| /** Disables local execution of a spawn. */ | |
| public static final String NO_LOCAL = "no-local"; | |
| /** Disables local sandboxing of a spawn. */ | |
| public static final String LEGACY_NOSANDBOX = "nosandbox"; | |
| /** Disables local sandboxing of a spawn. */ | |
| public static final String NO_SANDBOX = "no-sandbox"; | |
| /** | |
| * Set for Xcode-related rules. Used for quality control to make sure that all Xcode-dependent | |
| * rules propagate the necessary configurations. Begins with "supports" so as not to be filtered | |
| * out for Bazel by {@code TargetUtils}. | |
| */ | |
| public static final String REQUIREMENTS_SET = "supports-xcode-requirements-set"; | |
| /** | |
| * Enables networking for a spawn if possible (only if sandboxing is enabled and if the sandbox | |
| * supports it). | |
| */ | |
| public static final String REQUIRES_NETWORK = "requires-network"; | |
| /** | |
| * Disables networking for a spawn if possible (only if sandboxing is enabled and if the sandbox | |
| * supports it). | |
| */ | |
| public static final String BLOCK_NETWORK = "block-network"; | |
| /** | |
| * On linux, if sandboxing is enabled, ensures that a spawn is run with uid 0, i.e., root. Has no | |
| * effect otherwise. | |
| */ | |
| public static final String REQUIRES_FAKEROOT = "requires-fakeroot"; | |
| /** Suppress CLI reporting for this spawn - it's part of another action. */ | |
| public static final String DO_NOT_REPORT = "internal-do-not-report"; | |
| /** Use this to request eager fetching of a single remote output into local memory. */ | |
| public static final String REMOTE_EXECUTION_INLINE_OUTPUTS = "internal-inline-outputs"; | |
| /** Tag for Google internal use. Indicates a memory estimate in bytes. */ | |
| public static final String MEMORY_ESTIMATE = "internal-memory-estimate"; | |
| /** | |
| * Request graceful termination of subprocesses on interrupt (that is, an initial {@code SIGTERM} | |
| * followed by a {@code SIGKILL} after a grace period). | |
| */ | |
| public static final String GRACEFUL_TERMINATION = "supports-graceful-termination"; | |
| /** Requires the execution service to support a given Xcode version e.g. "xcode_version:1.0". */ | |
| public static final String REQUIRES_XCODE = "requires-xcode"; | |
| /** | |
| * Requires the execution service to support a "label" in addition to the Xcode version. The user | |
| * specifies the label as a hyphenated extension to their requested version. For example, if the | |
| * user requests "--xcode_version=1.0-unstable", the action request will include | |
| * "requires-xcode-label:unstable" and "requires-xcode:1.0". | |
| */ | |
| public static final String REQUIRES_XCODE_LABEL = "requires-xcode-label"; | |
| /** Requires the execution service do NOT share caches across different workspace. */ | |
| public static final String DIFFERENTIATE_WORKSPACE_CACHE = | |
| "internal-differentiate-workspace-cache"; | |
| /** | |
| * Indicates that the action is compatible with path mapping, e.g., removing the configuration | |
| * segment from the paths of all inputs and outputs. | |
| */ | |
| public static final String SUPPORTS_PATH_MAPPING = "supports-path-mapping"; | |
| /** | |
| * Indicates that the action is compatible with heuristic path mapping, e.g., removing the | |
| * configuration segment from the paths of all inputs and outputs and all custom arguments | |
| * containing them. | |
| * | |
| * <p>This requirement cannot be declared in rules/tags and can only be set via {@code | |
| * --modify_execution_info}. | |
| */ | |
| public static final String SUPPORTS_HEURISTIC_PATH_MAPPING = "supports-heuristic-path-mapping"; | |
| /** Indicates that the action was created as part of a {@code map_directory()} call. */ | |
| public static final String MAP_DIRECTORY_ACTION = "internal-map-directory-action"; | |
| } |