| // Copyright 2023 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.analysis.actions; |
| |
| import static com.google.common.collect.ImmutableList.toImmutableList; |
| |
| import com.google.common.base.Preconditions; |
| import com.google.common.collect.ImmutableList; |
| import com.google.common.collect.ImmutableMap; |
| import com.google.devtools.build.lib.actions.AbstractAction; |
| import com.google.devtools.build.lib.actions.ActionExecutionContext; |
| import com.google.devtools.build.lib.actions.ActionExecutionException; |
| import com.google.devtools.build.lib.actions.ActionKeyContext; |
| import com.google.devtools.build.lib.actions.ActionOwner; |
| import com.google.devtools.build.lib.actions.ActionResult; |
| import com.google.devtools.build.lib.actions.Artifact; |
| import com.google.devtools.build.lib.analysis.WorkspaceStatusAction; |
| import com.google.devtools.build.lib.cmdline.BazelModuleContext; |
| import com.google.devtools.build.lib.collect.nestedset.NestedSetBuilder; |
| import com.google.devtools.build.lib.collect.nestedset.Order; |
| import com.google.devtools.build.lib.server.FailureDetails.Execution; |
| import com.google.devtools.build.lib.server.FailureDetails.FailureDetail; |
| import com.google.devtools.build.lib.skyframe.WorkspaceStatusValue.BuildInfoKey; |
| import com.google.devtools.build.lib.util.DetailedExitCode; |
| import com.google.devtools.build.lib.util.Fingerprint; |
| import java.io.IOException; |
| import java.util.HashMap; |
| import java.util.Map; |
| import javax.annotation.Nullable; |
| import net.starlark.java.eval.Dict; |
| import net.starlark.java.eval.EvalException; |
| import net.starlark.java.eval.Mutability; |
| import net.starlark.java.eval.Starlark; |
| import net.starlark.java.eval.StarlarkFunction; |
| import net.starlark.java.eval.StarlarkSemantics; |
| import net.starlark.java.eval.StarlarkThread; |
| |
| /** |
| * Translates workspace status text files(<a |
| * href="https://bazel.build/rules/lib/ctx#info_file">ctx.info_file</a> and <a |
| * href="https://bazel.build/rules/lib/ctx#version_file">ctx.version_file</a>) to a language |
| * consumable file and writes its contents to output. Keys and values are translated by the callback |
| * translation_func Starlark method, and the output file format is generated according to the |
| * template. |
| * |
| * <p>Action takes text file as an input and transforms it with a user provided Starlark callback |
| * function to a dictionary of strings to strings. This dictionary is then used as substitutions to |
| * expand the user provided template file. |
| */ |
| public final class BuildInfoFileWriteAction extends AbstractAction { |
| |
| private static final String GUID = "7e4657a6-dd09-435e-9423-51d4846aad4a"; |
| |
| private final StarlarkFunction translationCallback; |
| private final Artifact template; |
| private final boolean isVolatile; |
| private final StarlarkSemantics semantics; |
| |
| public BuildInfoFileWriteAction( |
| ActionOwner owner, |
| Artifact input, |
| Artifact output, |
| StarlarkFunction translationCallback, |
| Artifact template, |
| boolean isVolatile, |
| StarlarkSemantics semantics) { |
| super( |
| owner, |
| NestedSetBuilder.create(Order.STABLE_ORDER, input, template), |
| ImmutableList.of(output)); |
| Preconditions.checkNotNull(translationCallback); |
| Preconditions.checkNotNull(template); |
| Preconditions.checkArgument( |
| input.getArtifactOwner() instanceof BuildInfoKey, |
| "input artifact of BuildInfoFileWriteAction must be one of workspace status artifacts:" |
| + " ctx.info_file or ctx.version_file"); |
| this.translationCallback = translationCallback; |
| this.template = template; |
| this.isVolatile = isVolatile; |
| this.semantics = semantics; |
| } |
| |
| @Override |
| public ActionResult execute(ActionExecutionContext ctx) |
| throws ActionExecutionException, InterruptedException { |
| Map<String, String> values = new HashMap<>(); |
| // Parse values from text file. |
| try { |
| Artifact valueFile = getPrimaryInput(); |
| values.putAll(WorkspaceStatusAction.parseValues(ctx.getInputPath(valueFile))); |
| } catch (IOException e) { |
| String message = "Failed to parse workspace status: " + e.getMessage(); |
| throw new ActionExecutionException( |
| message, |
| /* cause= */ e, |
| /* action= */ this, |
| /* catastrophe= */ false, |
| DetailedExitCode.of( |
| FailureDetail.newBuilder() |
| .setMessage(message) |
| .setExecution( |
| Execution.newBuilder().setCode(Execution.Code.SOURCE_INPUT_IO_EXCEPTION)) |
| .build())); |
| } |
| // Call Starlark callback function which takes workspace status file's |
| // content as an input and produces a dict which is written to the output. |
| Object substitutionDictObject = null; |
| try (Mutability mutability = Mutability.create("translate_build_info_file")) { |
| try { |
| StarlarkThread thread = new StarlarkThread(mutability, semantics); |
| substitutionDictObject = |
| Starlark.call( |
| thread, |
| translationCallback, |
| ImmutableList.of(Dict.immutableCopyOf(values)), |
| ImmutableMap.of()); |
| } catch (EvalException e) { |
| String message = |
| String.format( |
| "Error during translating %s status file : %s", |
| isVolatile ? "volatile" : "stable", e); |
| throw new ActionExecutionException( |
| message, |
| /* cause= */ e, |
| /* action= */ this, |
| /* catastrophe= */ false, |
| DetailedExitCode.of( |
| FailureDetail.newBuilder() |
| .setMessage(message) |
| .setExecution( |
| Execution.newBuilder().setCode(Execution.Code.NON_ACTION_EXECUTION_FAILURE)) |
| .build())); |
| } |
| Dict<String, String> substitutionDict = null; |
| try { |
| substitutionDict = |
| Dict.cast(substitutionDictObject, String.class, String.class, "substitution_dict"); |
| } catch (EvalException e) { |
| String message = |
| "BuildInfo translation callback function is expected to return dict of strings to" |
| + " strings, could not convert return value to Java type: " |
| + e; |
| throw new ActionExecutionException( |
| message, |
| /* cause= */ e, |
| /* action= */ this, |
| /* catastrophe= */ false, |
| DetailedExitCode.of( |
| FailureDetail.newBuilder() |
| .setMessage(message) |
| .setExecution( |
| Execution.newBuilder().setCode(Execution.Code.NON_ACTION_EXECUTION_FAILURE)) |
| .build())); |
| } |
| ImmutableList<Substitution> substitutionList = |
| substitutionDict.entrySet().stream() |
| .map(s -> Substitution.of(s.getKey(), s.getValue())) |
| .collect(toImmutableList()); |
| |
| return TemplateExpansionAction.execute( |
| /* actionExecutionContext= */ ctx, |
| /* action= */ this, |
| TemplateExpansionContext.TemplateMetadata.builder() |
| .setTemplate(Template.forArtifact(template)) |
| .setPrimaryOutput(getPrimaryOutput()) |
| .setSubstitutions(substitutionList) |
| .setMakeExecutable(false) |
| .build()); |
| } |
| } |
| |
| @Override |
| protected void computeKey( |
| ActionKeyContext actionKeyContext, |
| @Nullable Artifact.ArtifactExpander artifactExpander, |
| Fingerprint fp) { |
| fp.addString(GUID); |
| fp.addBoolean(isVolatile); |
| // Add Starlark function to the fingerprint. |
| fp.addBytes(BazelModuleContext.of(translationCallback.getModule()).bzlTransitiveDigest()); |
| } |
| |
| @Override |
| public String getMnemonic() { |
| return "TranslateBuildInfo"; |
| } |
| |
| @Override |
| protected String getRawProgressMessage() { |
| if (isVolatile) { |
| return "Translating volatile BuildInfo file"; |
| } else { |
| return "Translating stable BuildInfo file"; |
| } |
| } |
| |
| @Override |
| public boolean executeUnconditionally() { |
| return isVolatile; |
| } |
| |
| @Override |
| public boolean isVolatile() { |
| return isVolatile; |
| } |
| } |