aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/main/java/com/google/devtools/build/lib/rules/cpp/IncludeScanner.java
blob: 9c00efd64a25b5054f276708a15e7ab5f76d7f98 (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
175
176
177
// 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.Lists;
import com.google.common.collect.Sets;
import com.google.devtools.build.lib.actions.ActionExecutionContext;
import com.google.devtools.build.lib.actions.ActionExecutionException;
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.Executor;
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.Path;
import com.google.devtools.build.lib.vfs.PathFragment;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
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 a source file and a list of includes extracted from command line
   * flags. Adds all found files to the provided set {@code includes}. This
   * method takes into account the path- and file-level hints that are part of
   * this include scanner.
   */
  public void process(Artifact source, Map<Artifact, Path> legalOutputPaths,
      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. */
    IncludeScanner scannerFor(List<Path> quoteIncludePaths, List<Path> 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, ActionExecutionException {

      Set<Artifact> includes = Sets.newConcurrentHashSet();

      Executor executor = actionExecutionContext.getExecutor();
      Path execRoot = executor.getExecRoot();

      final List<Path> absoluteBuiltInIncludeDirs = new ArrayList<>();

      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, Path> legalOutputPaths = scannable.getLegalGeneratedScannerFileMap();
          List<PathFragment> includeDirs = new ArrayList<>(scannable.getIncludeDirs());
          List<PathFragment> quoteIncludeDirs = scannable.getQuoteIncludeDirs();
          List<String> cmdlineIncludes = scannable.getCmdlineIncludes();

          for (PathFragment pathFragment : scannable.getSystemIncludeDirs()) {
            includeDirs.add(pathFragment);
          }

          // Add the system include paths to the list of include paths.
          for (PathFragment pathFragment : action.getBuiltInIncludeDirectories()) {
            if (pathFragment.isAbsolute()) {
              absoluteBuiltInIncludeDirs.add(execRoot.getRelative(pathFragment));
            }
            includeDirs.add(pathFragment);
          }

          IncludeScanner scanner = includeScannerSupplier.scannerFor(
              relativeTo(execRoot, quoteIncludeDirs),
              relativeTo(execRoot, includeDirs));

          for (Artifact source : scannable.getIncludeScannerSources()) {
            // Add all include scanning entry points to the inputs; 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 cases:
            // 1. 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.
            // 2. 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, {@code 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.
            includes.add(source);
            scanner.process(source, legalOutputPaths, 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<>();
      IncludeProblems includeProblems = new IncludeProblems();
      for (Artifact included : includes) {
        if (FileSystemUtils.startsWithAny(included.getPath(), absoluteBuiltInIncludeDirs)) {
          // Skip include files found in absolute include directories. This currently only applies
          // to grte.
          continue;
        }
        if (included.getRoot().getPath().getParentDirectory() == null) {
          throw new UserExecException(
              "illegal absolute path to include file: " + included.getPath());
        }
        inputs.add(included);
      }
      return inputs;
    }

    private static List<Path> relativeTo(
        Path path, Collection<PathFragment> fragments) {
      List<Path> result = Lists.newArrayListWithCapacity(fragments.size());
      for (PathFragment fragment : fragments) {
        result.add(path.getRelative(fragment));
      }
      return result;
    }
  }
}