// 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.rules.cpp; import static java.nio.charset.StandardCharsets.ISO_8859_1; import static java.util.stream.Collectors.joining; 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.ActionEnvironment; import com.google.devtools.build.lib.actions.ActionExecutionContext; import com.google.devtools.build.lib.actions.ActionExecutionException; 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.actions.ExecException; import com.google.devtools.build.lib.actions.ResourceSet; import com.google.devtools.build.lib.actions.SpawnResult; import com.google.devtools.build.lib.collect.nestedset.NestedSet; import com.google.devtools.build.lib.collect.nestedset.NestedSetBuilder; import com.google.devtools.build.lib.concurrent.ThreadSafety.ThreadCompatible; import com.google.devtools.build.lib.events.Event; import com.google.devtools.build.lib.rules.cpp.CcCommon.CoptsFilter; import com.google.devtools.build.lib.rules.cpp.CcToolchainFeatures.FeatureConfiguration; import com.google.devtools.build.lib.util.ShellEscaper; import com.google.devtools.build.lib.vfs.FileSystemUtils; import com.google.devtools.build.lib.vfs.Path; import com.google.devtools.build.lib.vfs.PathFragment; import java.io.IOException; import java.util.List; import java.util.UUID; import java.util.logging.Logger; /** * Action that represents a fake C++ compilation step. */ @ThreadCompatible public class FakeCppCompileAction extends CppCompileAction { private static final Logger logger = Logger.getLogger(FakeCppCompileAction.class.getName()); public static final UUID GUID = UUID.fromString("8ab63589-be01-4a39-b770-b98ae8b03493"); private final PathFragment tempOutputFile; FakeCppCompileAction( ActionOwner owner, NestedSet allInputs, FeatureConfiguration featureConfiguration, CcToolchainVariables variables, Artifact sourceFile, CppConfiguration cppConfiguration, boolean shouldScanIncludes, boolean shouldPruneModules, boolean usePic, boolean useHeaderModules, NestedSet mandatoryInputs, Iterable inputsForInvalidation, ImmutableList builtinIncludeFiles, NestedSet prunableHeaders, Artifact outputFile, PathFragment tempOutputFile, DotdFile dotdFile, ActionEnvironment env, CcCompilationContext ccCompilationContext, CoptsFilter nocopts, CppSemantics cppSemantics, ImmutableList builtInIncludeDirectories, ImmutableMap executionInfo, Artifact grepIncludes) { super( owner, allInputs, featureConfiguration, variables, sourceFile, cppConfiguration, shouldScanIncludes, shouldPruneModules, usePic, useHeaderModules, mandatoryInputs, inputsForInvalidation, builtinIncludeFiles, prunableHeaders, outputFile, dotdFile, /* gcnoFile=*/ null, /* dwoFile=*/ null, /* ltoIndexingFile=*/ null, env, // We only allow inclusion of header files explicitly declared in // "srcs", so we only use declaredIncludeSrcs, not declaredIncludeDirs. // (Disallowing use of undeclared headers for cc_fake_binary is needed // because the header files get included in the runfiles for the // cc_fake_binary and for the negative compilation tests that depend on // the cc_fake_binary, and the runfiles must be determined at analysis // time, so they can't depend on the contents of the ".d" file.) CcCompilationContext.disallowUndeclaredHeaders(ccCompilationContext), nocopts, /* additionalIncludeScanningRoots=*/ ImmutableList.of(), GUID, executionInfo, CppActionNames.CPP_COMPILE, cppSemantics, builtInIncludeDirectories, grepIncludes); this.tempOutputFile = Preconditions.checkNotNull(tempOutputFile); } @Override @ThreadCompatible public ActionResult execute(ActionExecutionContext actionExecutionContext) throws ActionExecutionException, InterruptedException { setModuleFileFlags(); List spawnResults; // First, do a normal compilation, to generate the ".d" file. The generated object file is built // to a temporary location (tempOutputFile) and ignored afterwards. logger.info("Generating " + getDotdFile()); CppCompileActionContext context = actionExecutionContext.getContext(CppCompileActionContext.class); CppCompileActionContext.Reply reply = null; try { CppCompileActionResult cppCompileActionResult = context.execWithReply(this, actionExecutionContext); reply = cppCompileActionResult.contextReply(); spawnResults = cppCompileActionResult.spawnResults(); } catch (ExecException e) { throw e.toActionExecutionException( "C++ compilation of rule '" + getOwner().getLabel() + "'", actionExecutionContext.getVerboseFailures(), this); } CppIncludeExtractionContext scanningContext = actionExecutionContext.getContext(CppIncludeExtractionContext.class); Path execRoot = actionExecutionContext.getExecRoot(); NestedSet discoveredInputs; if (getDotdFile() == null) { discoveredInputs = NestedSetBuilder.stableOrder().build(); } else { HeaderDiscovery.Builder discoveryBuilder = new HeaderDiscovery.Builder() .setAction(this) .setSourceFile(getSourceFile()) .setDependencies( processDepset(actionExecutionContext, execRoot, reply).getDependencies()) .setPermittedSystemIncludePrefixes(getPermittedSystemIncludePrefixes(execRoot)) .setAllowedDerivedinputs(getAllowedDerivedInputs()); if (needsIncludeValidation) { discoveryBuilder.shouldValidateInclusions(); } discoveredInputs = discoveryBuilder .build() .discoverInputsFromDependencies(execRoot, scanningContext.getArtifactResolver()); } reply = null; // Clear in-memory .d files early. // Even cc_fake_binary rules need to properly declare their dependencies... // In fact, they need to declare their dependencies even more than cc_binary rules do. // CcCommonConfiguredTarget passes in an empty set of declaredIncludeDirs, // so this check below will only allow inclusion of header files that are explicitly // listed in the "srcs" of the cc_fake_binary or in the "srcs" of a cc_library that it // depends on. try { validateInclusions(actionExecutionContext, discoveredInputs); } catch (ActionExecutionException e) { // TODO(bazel-team): (2009) make this into an error, once most of the current warnings // are fixed. actionExecutionContext .getEventHandler() .handle( Event.warn( getOwner().getLocation(), e.getMessage() + ";\n this warning may eventually become an error")); } updateActionInputs(discoveredInputs); // Generate a fake ".o" file containing the command line needed to generate // the real object file. logger.info("Generating " + outputFile); // A cc_fake_binary rule generates fake .o files and a fake target file, // which merely contain instructions on building the real target. We need to // be careful to use a new set of output file names in the instructions, as // to not overwrite the fake output files when someone tries to follow the // instructions. As the real compilation is executed by the test from its // runfiles directory (where writing is forbidden), we patch the command // line to write to $TEST_TMPDIR instead. final String outputPrefix = "$TEST_TMPDIR/"; String argv = getArguments() .stream() .map( input -> { String result = ShellEscaper.escapeString(input); // Replace -c so it's -c . if (input.equals(tempOutputFile.getPathString())) { result = outputPrefix + ShellEscaper.escapeString(outputFile.getExecPathString()); } if (input.equals(outputFile.getExecPathString()) || (getDotdFile() != null && input.equals(getDotdFile().getSafeExecPath().getPathString()))) { result = outputPrefix + ShellEscaper.escapeString(input); } return result; }) .collect(joining(" ")); // Write the command needed to build the real .o file to the fake .o file. // Generate a command to ensure that the output directory exists; otherwise // the compilation would fail. try { // Ensure that the .d file and .o file are siblings, so that the "mkdir" below works for // both. Preconditions.checkState( getDotdFile() == null || outputFile .getExecPath() .getParentDirectory() .equals(getDotdFile().getSafeExecPath().getParentDirectory())); FileSystemUtils.writeContent( actionExecutionContext.getInputPath(outputFile), ISO_8859_1, actionExecutionContext.getInputPath(outputFile).getBaseName() + ": " + "mkdir -p " + outputPrefix + "$(dirname " + outputFile.getExecPath() + ")" + " && " + argv + "\n"); } catch (IOException e) { throw new ActionExecutionException("failed to create fake compile command for rule '" + getOwner().getLabel() + ": " + e.getMessage(), this, false); } return ActionResult.create(spawnResults); } @Override public String getMnemonic() { return "FakeCppCompile"; } @Override public ResourceSet estimateResourceConsumptionLocal() { return ResourceSet.createWithRamCpuIo(/*memoryMb=*/1, /*cpuUsage=*/0.1, /*ioUsage=*/0.0); } }