| // 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) |
| 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); |
| } |
| } 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) { |
| if (FileSystemUtils.startsWithAny(included.getPath().asFragment(), |
| absoluteBuiltInIncludeDirs)) { |
| // Skip include files found in absolute include directories. |
| continue; |
| } |
| if (included.getRoot().getPath().getParentDirectory() == null) { |
| throw new UserExecException( |
| "illegal absolute path to include file: " + included.getPath()); |
| } |
| inputs.add(included); |
| } |
| return inputs; |
| } |
| } |
| } |