com.google.devtools.build.lib.rules.genrule.GenRuleBase.java Source code

Java tutorial

Introduction

Here is the source code for com.google.devtools.build.lib.rules.genrule.GenRuleBase.java

Source

// Copyright 2017 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.rules.genrule;

import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.common.collect.Maps;
import com.google.devtools.build.lib.actions.Artifact;
import com.google.devtools.build.lib.actions.CompositeRunfilesSupplier;
import com.google.devtools.build.lib.analysis.CommandHelper;
import com.google.devtools.build.lib.analysis.ConfigurationMakeVariableContext;
import com.google.devtools.build.lib.analysis.ConfiguredTarget;
import com.google.devtools.build.lib.analysis.FileProvider;
import com.google.devtools.build.lib.analysis.FilesToRunProvider;
import com.google.devtools.build.lib.analysis.MakeVariableExpander.ExpansionException;
import com.google.devtools.build.lib.analysis.RuleConfiguredTarget.Mode;
import com.google.devtools.build.lib.analysis.RuleConfiguredTargetBuilder;
import com.google.devtools.build.lib.analysis.RuleContext;
import com.google.devtools.build.lib.analysis.Runfiles;
import com.google.devtools.build.lib.analysis.RunfilesProvider;
import com.google.devtools.build.lib.analysis.TransitiveInfoCollection;
import com.google.devtools.build.lib.cmdline.Label;
import com.google.devtools.build.lib.collect.nestedset.NestedSet;
import com.google.devtools.build.lib.collect.nestedset.NestedSetBuilder;
import com.google.devtools.build.lib.collect.nestedset.Order;
import com.google.devtools.build.lib.packages.TargetUtils;
import com.google.devtools.build.lib.rules.AliasProvider;
import com.google.devtools.build.lib.rules.MakeVariableProvider;
import com.google.devtools.build.lib.rules.RuleConfiguredTargetFactory;
import com.google.devtools.build.lib.rules.cpp.CppHelper;
import com.google.devtools.build.lib.rules.java.JavaHelper;
import com.google.devtools.build.lib.syntax.Type;
import com.google.devtools.build.lib.vfs.PathFragment;
import java.util.List;
import java.util.Map;
import java.util.regex.Pattern;

/**
 * A base implementation of genrule, to be used by specific implementing rules which can change some
 * of the semantics around when the execution info and inputs are changed.
 */
public abstract class GenRuleBase implements RuleConfiguredTargetFactory {

    private static final Pattern CROSSTOOL_MAKE_VARIABLE = Pattern
            .compile("\\$\\((CC|AR|NM|OBJCOPY|STRIP|GCOVTOOL)\\)");
    private static final Pattern JDK_MAKE_VARIABLE = Pattern.compile("\\$\\((JAVABASE|JAVA)\\)");

    protected static boolean requiresCrosstool(String command) {
        return CROSSTOOL_MAKE_VARIABLE.matcher(command).find();
    }

    protected static boolean requiresJdk(String command) {
        return JDK_MAKE_VARIABLE.matcher(command).find();
    }

    /**
     * Returns a {@link Map} of execution info, which will be used in later processing to construct
     * the actual command line that will be executed.
     *
     * <p>GenRule implementations can override this method to include additional specific information
     * needed.
     */
    protected Map<String, String> getExtraExecutionInfo(RuleContext ruleContext, String command) {
        return ImmutableMap.of();
    }

    /**
     * Returns an {@link Iterable} of {@link NestedSet}s, which will be added to the genrule's inputs
     * using the {@link NestedSetBuilder#addTransitive} method.
     *
     * <p>GenRule implementations can override this method to better control what inputs are needed
     * for specific command inputs.
     */
    protected Iterable<NestedSet<Artifact>> getExtraInputArtifacts(RuleContext ruleContext, String command) {
        return ImmutableList.of();
    }

    /**
     * Returns {@code true} if the rule should be stamped.
     *
     * <p>Genrule implementations can set this based on the rule context, including by defining their
     * own attributes over and above what is present in {@link GenRuleBaseRule}.
     */
    protected abstract boolean isStampingEnabled(RuleContext ruleContext);

    /**
     * Updates the {@link RuleConfiguredTargetBuilder} that is used for this rule.
     *
     * <p>GenRule implementations can override this method to enhance and update the builder without
     * needing to entirely override the {@link #create} method.
     */
    protected RuleConfiguredTargetBuilder updateBuilder(RuleConfiguredTargetBuilder builder,
            RuleContext ruleContext, NestedSet<Artifact> filesToBuild) {
        return builder;
    }

    @Override
    public ConfiguredTarget create(RuleContext ruleContext) throws RuleErrorException, InterruptedException {
        NestedSet<Artifact> filesToBuild = NestedSetBuilder.wrap(Order.STABLE_ORDER,
                ruleContext.getOutputArtifacts());
        NestedSetBuilder<Artifact> resolvedSrcsBuilder = NestedSetBuilder.stableOrder();

        if (filesToBuild.isEmpty()) {
            ruleContext.attributeError("outs", "Genrules without outputs don't make sense");
        }
        if (ruleContext.attributes().get("executable", Type.BOOLEAN) && Iterables.size(filesToBuild) > 1) {
            ruleContext.attributeError("executable",
                    "if genrules produce executables, they are allowed only one output. "
                            + "If you need the executable=1 argument, then you should split this genrule into "
                            + "genrules producing single outputs");
        }

        ImmutableMap.Builder<Label, NestedSet<Artifact>> labelMap = ImmutableMap.builder();
        for (TransitiveInfoCollection dep : ruleContext.getPrerequisites("srcs", Mode.TARGET)) {
            // This target provides specific types of files for genrules.
            GenRuleSourcesProvider provider = dep.getProvider(GenRuleSourcesProvider.class);
            NestedSet<Artifact> files = (provider != null) ? provider.getGenruleFiles()
                    : dep.getProvider(FileProvider.class).getFilesToBuild();
            resolvedSrcsBuilder.addTransitive(files);
            labelMap.put(AliasProvider.getDependencyLabel(dep), files);
        }
        NestedSet<Artifact> resolvedSrcs = resolvedSrcsBuilder.build();

        CommandHelper commandHelper = new CommandHelper(ruleContext,
                ruleContext.getPrerequisites("tools", Mode.HOST), labelMap.build());

        if (ruleContext.hasErrors()) {
            return null;
        }

        String baseCommand = commandHelper.resolveCommandAndExpandLabels(
                ruleContext.attributes().get("heuristic_label_expansion", Type.BOOLEAN), false);

        // Adds the genrule environment setup script before the actual shell command
        String command = String.format("source %s; %s",
                ruleContext.getPrerequisiteArtifact("$genrule_setup", Mode.HOST).getExecPath(), baseCommand);

        command = resolveCommand(command, ruleContext, resolvedSrcs, filesToBuild);

        String message = ruleContext.attributes().get("message", Type.STRING);
        if (message.isEmpty()) {
            message = "Executing genrule";
        }

        ImmutableMap<String, String> env = ruleContext.getConfiguration().getLocalShellEnvironment();
        ImmutableSet<String> clientEnvVars = ruleContext.getConfiguration().getVariableShellEnvironment();

        Map<String, String> executionInfo = Maps.newLinkedHashMap();
        executionInfo.putAll(TargetUtils.getExecutionInfo(ruleContext.getRule()));

        if (ruleContext.attributes().get("local", Type.BOOLEAN)) {
            executionInfo.put("local", "");
        }

        executionInfo.putAll(getExtraExecutionInfo(ruleContext, baseCommand));

        NestedSetBuilder<Artifact> inputs = NestedSetBuilder.stableOrder();
        inputs.addAll(resolvedSrcs);
        inputs.addAll(commandHelper.getResolvedTools());
        FilesToRunProvider genruleSetup = ruleContext.getPrerequisite("$genrule_setup", Mode.HOST,
                FilesToRunProvider.class);
        inputs.addAll(genruleSetup.getFilesToRun());
        List<String> argv = commandHelper.buildCommandLine(command, inputs, ".genrule_script.sh",
                ImmutableMap.copyOf(executionInfo));

        // TODO(bazel-team): Make the make variable expander pass back a list of these.
        if (requiresCrosstool(baseCommand)) {
            // If cc is used, silently throw in the crosstool filegroup as a dependency.
            inputs.addTransitive(CppHelper.getToolchain(ruleContext, ":cc_toolchain").getCrosstoolMiddleman());
        }
        if (requiresJdk(baseCommand)) {
            // If javac is used, silently throw in the jdk filegroup as a dependency.
            // Note we expand Java-related variables with the *host* configuration.
            inputs.addTransitive(JavaHelper.getHostJavabaseInputs(ruleContext));
        }

        for (NestedSet<Artifact> extraInputs : getExtraInputArtifacts(ruleContext, baseCommand)) {
            inputs.addTransitive(extraInputs);
        }

        if (isStampingEnabled(ruleContext)) {
            inputs.add(ruleContext.getAnalysisEnvironment().getStableWorkspaceStatusArtifact());
            inputs.add(ruleContext.getAnalysisEnvironment().getVolatileWorkspaceStatusArtifact());
        }

        ruleContext.registerAction(new GenRuleAction(ruleContext.getActionOwner(),
                ImmutableList.copyOf(commandHelper.getResolvedTools()), inputs.build(), filesToBuild, argv, env,
                clientEnvVars, ImmutableMap.copyOf(executionInfo),
                new CompositeRunfilesSupplier(commandHelper.getToolsRunfilesSuppliers()),
                message + ' ' + ruleContext.getLabel()));

        RunfilesProvider runfilesProvider = RunfilesProvider.withData(
                // No runfiles provided if not a data dependency.
                Runfiles.EMPTY,
                // We only need to consider the outputs of a genrule
                // No need to visit the dependencies of a genrule. They cross from the target into the host
                // configuration, because the dependencies of a genrule are always built for the host
                // configuration.
                new Runfiles.Builder(ruleContext.getWorkspaceName(),
                        ruleContext.getConfiguration().legacyExternalRunfiles())
                                .addTransitiveArtifacts(filesToBuild).build());

        RuleConfiguredTargetBuilder builder = new RuleConfiguredTargetBuilder(ruleContext)
                .setFilesToBuild(filesToBuild).setRunfilesSupport(null, getExecutable(ruleContext, filesToBuild))
                .addProvider(RunfilesProvider.class, runfilesProvider);

        builder = updateBuilder(builder, ruleContext, filesToBuild);
        return builder.build();
    }

    /**
     * Returns the executable artifact, if the rule is marked as executable and there is only one
     * artifact.
     */
    private static Artifact getExecutable(RuleContext ruleContext, NestedSet<Artifact> filesToBuild) {
        if (!ruleContext.attributes().get("executable", Type.BOOLEAN)) {
            return null;
        }
        if (Iterables.size(filesToBuild) == 1) {
            return Iterables.getOnlyElement(filesToBuild);
        }
        return null;
    }

    /**
     * Resolves any variables, including make and genrule-specific variables, in the command and
     * returns the expanded command.
     *
     * <p>GenRule implementations may override this method to perform additional expansions.
     */
    protected String resolveCommand(String command, final RuleContext ruleContext,
            final NestedSet<Artifact> resolvedSrcs, final NestedSet<Artifact> filesToBuild) {
        return ruleContext.expandMakeVariables("cmd", command,
                createCommandResolverContext(ruleContext, resolvedSrcs, filesToBuild));
    }

    /**
     * Creates a new {@link CommandResolverContext} instance to use in {@link #resolveCommand}.
     */
    protected CommandResolverContext createCommandResolverContext(RuleContext ruleContext,
            NestedSet<Artifact> resolvedSrcs, NestedSet<Artifact> filesToBuild) {
        return new CommandResolverContext(ruleContext, resolvedSrcs, filesToBuild);
    }

    /**
     * Implementation of {@link ConfigurationMakeVariableContext} used to expand variables in a
     * genrule command string.
     */
    protected static class CommandResolverContext extends ConfigurationMakeVariableContext {

        private final RuleContext ruleContext;
        private final NestedSet<Artifact> resolvedSrcs;
        private final NestedSet<Artifact> filesToBuild;

        public CommandResolverContext(RuleContext ruleContext, NestedSet<Artifact> resolvedSrcs,
                NestedSet<Artifact> filesToBuild) {
            super(ruleContext.getRule().getPackage(), ruleContext.getConfiguration(),
                    MakeVariableProvider.getToolchainMakeVariables(ruleContext, "toolchains"));
            this.ruleContext = ruleContext;
            this.resolvedSrcs = resolvedSrcs;
            this.filesToBuild = filesToBuild;
        }

        public RuleContext getRuleContext() {
            return ruleContext;
        }

        @Override
        public String lookupMakeVariable(String name) throws ExpansionException {
            if (name.equals("SRCS")) {
                return Artifact.joinExecPaths(" ", resolvedSrcs);
            } else if (name.equals("<")) {
                return expandSingletonArtifact(resolvedSrcs, "$<", "input file");
            } else if (name.equals("OUTS")) {
                return Artifact.joinExecPaths(" ", filesToBuild);
            } else if (name.equals("@")) {
                return expandSingletonArtifact(filesToBuild, "$@", "output file");
            } else if (name.equals("@D")) {
                // The output directory. If there is only one filename in outs,
                // this expands to the directory containing that file. If there are
                // multiple filenames, this variable instead expands to the
                // package's root directory in the genfiles tree, even if all the
                // generated files belong to the same subdirectory!
                if (Iterables.size(filesToBuild) == 1) {
                    Artifact outputFile = Iterables.getOnlyElement(filesToBuild);
                    PathFragment relativeOutputFile = outputFile.getExecPath();
                    if (relativeOutputFile.segmentCount() <= 1) {
                        // This should never happen, since the path should contain at
                        // least a package name and a file name.
                        throw new IllegalStateException(
                                "$(@D) for genrule " + ruleContext.getLabel() + " has less than one segment");
                    }
                    return relativeOutputFile.getParentDirectory().getPathString();
                } else {
                    PathFragment dir;
                    if (ruleContext.getRule().hasBinaryOutput()) {
                        dir = ruleContext.getConfiguration().getBinFragment();
                    } else {
                        dir = ruleContext.getConfiguration().getGenfilesFragment();
                    }
                    PathFragment relPath = ruleContext.getRule().getLabel().getPackageIdentifier().getSourceRoot();
                    return dir.getRelative(relPath).getPathString();
                }
            } else if (JDK_MAKE_VARIABLE.matcher("$(" + name + ")").find()) {
                return new ConfigurationMakeVariableContext(ruleContext.getTarget().getPackage(),
                        ruleContext.getHostConfiguration()).lookupMakeVariable(name);
            } else {
                return super.lookupMakeVariable(name);
            }
        }

        /**
         * Returns the path of the sole element "artifacts", generating an exception with an informative
         * error message iff the set is not a singleton. Used to expand "$<", "$@".
         */
        private final String expandSingletonArtifact(Iterable<Artifact> artifacts, String variable,
                String artifactName) throws ExpansionException {
            if (Iterables.isEmpty(artifacts)) {
                throw new ExpansionException("variable '" + variable + "' : no " + artifactName);
            } else if (Iterables.size(artifacts) > 1) {
                throw new ExpansionException("variable '" + variable + "' : more than one " + artifactName);
            }
            return Iterables.getOnlyElement(artifacts).getExecPathString();
        }
    }
}