aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/main/java/com/google/devtools/build/lib/rules/cpp/IncludeScanner.java
blob: 872a3251328c5df7121bda2e20b0b5a53ce206a9 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
// Copyright 2014 Google Inc. 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<>();
      Artifact builtInInclude = action.getBuiltInIncludeFile();
      if (builtInInclude != null) {
        includes.add(builtInInclude);
      }

      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;
    }
  }
}