blob: 782028712a143ae28148045390e27b258ecf7f9c [file] [log] [blame]
// 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 com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
import com.google.common.collect.Sets;
import com.google.devtools.build.lib.actions.ActionExecutionContext;
import com.google.devtools.build.lib.actions.Artifact;
import com.google.devtools.build.lib.actions.EnvironmentalExecException;
import com.google.devtools.build.lib.actions.ExecException;
import com.google.devtools.build.lib.actions.UserExecException;
import com.google.devtools.build.lib.profiler.Profiler;
import com.google.devtools.build.lib.profiler.ProfilerTask;
import com.google.devtools.build.lib.vfs.FileSystemUtils;
import com.google.devtools.build.lib.vfs.PathFragment;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* Scans source files to determine the bounding set of transitively referenced include files.
*
* <p>Note that include scanning is performance-critical code.
*/
public interface IncludeScanner {
/**
* Processes source files and a list of includes extracted from command line flags. Adds all found
* files to the provided set {@code includes}.
*
* <p>The resulting set will include {@code mainSource} and {@code sources}. This has no real
* impact in the case that we are scanning a single source file, since it is already known to be
* an input. However, this is necessary when we have more than one source to scan from, for
* example when building C++ modules. In that case we have one of two possibilities:
* <ol>
* <li>We compile a header module - there, the .cppmap file is the main source file (which we do
* not include-scan, as that would require an extra parser), and thus already in the input;
* all headers in the .cppmap file are our entry points for include scanning, but are not yet
* in the inputs - they get added here.</li>
* <li>We compile an object file that uses a header module; currently using a header module
* requires all headers it can reference to be available for the compilation. The header
* module can reference headers that are not in the transitive include closure of the current
* translation unit. Therefore, {@link CppCompileAction} adds all headers specified
* transitively for compiled header modules as include scanning entry points, and we need to
* add the entry points to the inputs here.</li></ol>
* </p>
*
* <p>{@code mainSource} is the source file relative to which the {@code cmdlineIncludes} are
* interpreted.</p>
*/
void process(Artifact mainSource, Collection<Artifact> sources,
Map<Artifact, Artifact> legalOutputPaths, List<PathFragment> includeDirs,
List<PathFragment> quoteIncludeDirs, List<String> cmdlineIncludes,
Set<Artifact> includes, ActionExecutionContext actionExecutionContext, Artifact grepIncludes)
throws IOException, ExecException, InterruptedException;
/** Supplies IncludeScanners upon request. */
interface IncludeScannerSupplier {
/**
* Returns the possibly shared scanner to be used for a given pair of include paths. The paths
* are specified as PathFragments relative to the execution root.
*/
IncludeScanner scannerFor(List<PathFragment> quoteIncludePaths,
List<PathFragment> includePaths);
}
/**
* Helper class that exists just to provide a static method that prepares the arguments with which
* to call an IncludeScanner.
*/
class IncludeScanningPreparer {
private IncludeScanningPreparer() {}
/**
* Returns the files transitively included by the source files of the given IncludeScannable.
*
* @param action IncludeScannable whose sources' transitive includes will be returned.
* @param includeScannerSupplier supplies IncludeScanners to actually do the transitive
* scanning (and caching results) for a given source file.
* @param actionExecutionContext the context for {@code action}.
* @param profilerTaskName what the {@link Profiler} should record this call for.
*/
public static Collection<Artifact> scanForIncludedInputs(IncludeScannable action,
IncludeScannerSupplier includeScannerSupplier,
ActionExecutionContext actionExecutionContext,
String profilerTaskName)
throws ExecException, InterruptedException {
Set<Artifact> includes = Sets.newConcurrentHashSet();
final List<PathFragment> absoluteBuiltInIncludeDirs = new ArrayList<>();
includes.addAll(action.getBuiltInIncludeFiles());
Profiler profiler = Profiler.instance();
try {
profiler.startTask(ProfilerTask.SCANNER, profilerTaskName);
// We need to scan the action itself, but also the auxiliary scannables
// (for LIPO). There is no need to call getAuxiliaryScannables
// recursively.
for (IncludeScannable scannable :
Iterables.concat(ImmutableList.of(action), action.getAuxiliaryScannables())) {
Map<Artifact, Artifact> legalOutputPaths = scannable.getLegalGeneratedScannerFileMap();
// Deduplicate include directories. This can occur especially with "built-in" and "system"
// include directories because of the way we retrieve them. Duplicate include directories
// really mess up #include_next directives.
Set<PathFragment> includeDirs = new LinkedHashSet<>(scannable.getIncludeDirs());
List<PathFragment> quoteIncludeDirs = scannable.getQuoteIncludeDirs();
List<String> cmdlineIncludes = scannable.getCmdlineIncludes();
includeDirs.addAll(scannable.getSystemIncludeDirs());
// Add the system include paths to the list of include paths.
for (PathFragment pathFragment : action.getBuiltInIncludeDirectories()) {
if (pathFragment.isAbsolute()) {
absoluteBuiltInIncludeDirs.add(pathFragment);
}
includeDirs.add(pathFragment);
}
List<PathFragment> includeDirList = ImmutableList.copyOf(includeDirs);
IncludeScanner scanner = includeScannerSupplier.scannerFor(quoteIncludeDirs,
includeDirList);
Artifact mainSource = scannable.getMainIncludeScannerSource();
Collection<Artifact> sources = scannable.getIncludeScannerSources();
scanner.process(mainSource, sources, legalOutputPaths, quoteIncludeDirs,
includeDirList, cmdlineIncludes, includes, actionExecutionContext,
action.getGrepIncludes());
}
} catch (IOException e) {
throw new EnvironmentalExecException(e.getMessage());
} finally {
profiler.completeTask(ProfilerTask.SCANNER);
}
// Collect inputs and output
List<Artifact> inputs = new ArrayList<>();
for (Artifact included : includes) {
// Check for absolute includes -- we assign the file system root as
// the root path for such includes
if (included.getRoot().getRoot().isAbsolute()) {
if (FileSystemUtils.startsWithAny(
actionExecutionContext.getInputPath(included).asFragment(),
absoluteBuiltInIncludeDirs)) {
// Skip include files found in absolute include directories.
continue;
}
throw new UserExecException(
"illegal absolute path to include file: "
+ actionExecutionContext.getInputPath(included));
}
inputs.add(included);
}
return inputs;
}
}
}