| // 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.objc; |
| |
| import com.google.common.annotations.VisibleForTesting; |
| import com.google.common.collect.ImmutableList; |
| import com.google.devtools.build.lib.actions.ActionExecutionContext; |
| import com.google.devtools.build.lib.actions.Artifact; |
| import com.google.devtools.build.lib.actions.ArtifactPathResolver; |
| 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.collect.nestedset.NestedSet; |
| import com.google.devtools.build.lib.rules.cpp.CppCompileAction; |
| import com.google.devtools.build.lib.rules.cpp.IncludeProcessing; |
| import com.google.devtools.build.lib.rules.cpp.IncludeScanner.IncludeScannerSupplier; |
| import com.google.devtools.build.lib.rules.cpp.IncludeScanner.IncludeScanningHeaderData; |
| import com.google.devtools.build.lib.skyframe.serialization.autocodec.AutoCodec; |
| import com.google.devtools.build.lib.vfs.FileSystemUtils; |
| import com.google.devtools.build.lib.vfs.PathFragment; |
| import java.io.IOException; |
| import java.nio.charset.StandardCharsets; |
| import java.util.ArrayList; |
| import java.util.HashMap; |
| import java.util.List; |
| import java.util.Map; |
| import javax.annotation.Nullable; |
| |
| /** |
| * Returns all inclusions that were discovered by the header scanner tool to implement the header |
| * thinning feature. |
| * |
| * <p>Reads the .headers_list output file if one was generated for the actions source file and |
| * returns the Artifact objects associated with the headers that were found. |
| */ |
| @AutoCodec |
| public class HeaderThinning implements IncludeProcessing { |
| private final Iterable<Artifact> potentialInputs; |
| |
| public HeaderThinning(Iterable<Artifact> potentialInputs) { |
| // Just store this, don't create map of potential inputs at construction so that it is done |
| // later at execution time rather than analysis time when this is instantiated. |
| this.potentialInputs = potentialInputs; |
| } |
| |
| private Map<PathFragment, Artifact> getAllowedInputsMap() { |
| Map<PathFragment, Artifact> allowedInputsMap = new HashMap<>(); |
| for (Artifact input : potentialInputs) { |
| allowedInputsMap.put(input.getExecPath(), input); |
| } |
| return allowedInputsMap; |
| } |
| |
| @Nullable |
| private static Artifact findHeadersListFile(NestedSet<Artifact> artifacts) { |
| for (Artifact artifact : artifacts) { |
| if (artifact.getExtension().equals("headers_list")) { |
| return artifact; |
| } |
| } |
| return null; |
| } |
| |
| @Override |
| public Iterable<Artifact> determineAdditionalInputs( |
| @Nullable IncludeScannerSupplier includeScannerSupplier, |
| CppCompileAction action, |
| ActionExecutionContext actionExecutionContext, |
| IncludeScanningHeaderData includeScanningHeaderData) |
| throws ExecException { |
| Artifact headersListFile = findHeadersListFile(action.getMandatoryInputs()); |
| if (headersListFile == null) { |
| return null; |
| } |
| return findRequiredHeaderInputs(action.getSourceFile(), headersListFile, getAllowedInputsMap(), |
| actionExecutionContext == null |
| ? ArtifactPathResolver.IDENTITY |
| : actionExecutionContext.getPathResolver()); |
| } |
| |
| /** |
| * Reads the header scanning output file and discovers all of those headers as input artifacts. |
| * |
| * @param sourceFile the source that requires these headers |
| * @param headersListFile .headers_list file output from header_scanner tool to be read |
| * @param inputArtifactsMap map of PathFragment to Artifact of possible headers |
| * @param pathResolver used to read the headersListFile |
| * @return collection of header artifacts that are required for {@code action} to compile |
| * @throws ExecException on environmental (IO) or user errors |
| */ |
| @VisibleForTesting |
| static Iterable<Artifact> findRequiredHeaderInputs( |
| Artifact sourceFile, Artifact headersListFile, Map<PathFragment, Artifact> inputArtifactsMap, |
| ArtifactPathResolver pathResolver) |
| throws ExecException { |
| try { |
| ImmutableList.Builder<Artifact> includeBuilder = ImmutableList.builder(); |
| List<PathFragment> missing = new ArrayList<>(); |
| for (String line : |
| FileSystemUtils.readLines(pathResolver.toPath(headersListFile), StandardCharsets.UTF_8)) { |
| if (line.isEmpty()) { |
| continue; |
| } |
| |
| PathFragment headerPath = PathFragment.create(line); |
| Artifact header = inputArtifactsMap.get(headerPath); |
| if (header == null) { |
| missing.add(headerPath); |
| } else { |
| includeBuilder.add(header); |
| } |
| } |
| |
| if (!missing.isEmpty()) { |
| includeBuilder.addAll( |
| findRequiredHeaderInputsInTreeArtifacts(sourceFile, inputArtifactsMap, missing)); |
| } |
| return includeBuilder.build(); |
| } catch (IOException ex) { |
| throw new EnvironmentalExecException( |
| String.format("Error reading headers file %s", headersListFile.getExecPathString()), ex); |
| } |
| } |
| |
| /** |
| * Headers inside a TreeArtifact will not have their ExecPath as a key in the map as they do not |
| * have their own Artifact object. These headers must be mapped to their containing TreeArtifact. |
| * We are unable to select individual files from within a TreeArtifact so must discover the entire |
| * TreeArtifact as an input. |
| */ |
| private static Iterable<Artifact> findRequiredHeaderInputsInTreeArtifacts( |
| Artifact sourceFile, |
| Map<PathFragment, Artifact> inputArtifactsMap, |
| List<PathFragment> missing) |
| throws ExecException { |
| ImmutableList.Builder<Artifact> includeBuilder = ImmutableList.builder(); |
| ImmutableList.Builder<PathFragment> treeArtifactPathsBuilder = ImmutableList.builder(); |
| for (Map.Entry<PathFragment, Artifact> inputEntry : inputArtifactsMap.entrySet()) { |
| if (inputEntry.getValue().isTreeArtifact()) { |
| treeArtifactPathsBuilder.add(inputEntry.getKey()); |
| } |
| } |
| |
| ImmutableList<PathFragment> treeArtifactPaths = treeArtifactPathsBuilder.build(); |
| for (PathFragment missingPath : missing) { |
| includeBuilder.add( |
| findRequiredHeaderInputInTreeArtifacts( |
| sourceFile, treeArtifactPaths, inputArtifactsMap, missingPath)); |
| } |
| return includeBuilder.build(); |
| } |
| |
| private static Artifact findRequiredHeaderInputInTreeArtifacts( |
| Artifact sourceFile, |
| List<PathFragment> treeArtifactPaths, |
| Map<PathFragment, Artifact> inputArtifactsMap, |
| PathFragment missingPath) |
| throws ExecException { |
| for (PathFragment treeArtifactPath : treeArtifactPaths) { |
| if (missingPath.startsWith(treeArtifactPath)) { |
| return inputArtifactsMap.get(treeArtifactPath); |
| } |
| } |
| |
| throw new UserExecException( |
| String.format( |
| "Unable to map header file (%s) found during header scanning of %s." |
| + " This is usually the result of a case mismatch", |
| missingPath, sourceFile.getExecPathString())); |
| } |
| } |