// 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.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.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.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 java.util.Map.Entry; 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. */ 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) throws ExecException { Artifact headersListFile = findHeadersListFile(action.getMandatoryInputs()); if (headersListFile == null) { return null; } return findRequiredHeaderInputs(action.getSourceFile(), headersListFile, getAllowedInputsMap()); } /** * 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 * @return collection of header artifacts that are required for {@code action} to compile * @throws ExecException on environmental (IO) or user errors */ public static Iterable<Artifact> findRequiredHeaderInputs( Artifact sourceFile, Artifact headersListFile, Map<PathFragment, Artifact> inputArtifactsMap) throws ExecException { try { ImmutableList.Builder<Artifact> includeBuilder = ImmutableList.builder(); List<PathFragment> missing = new ArrayList<>(); for (String line : FileSystemUtils.readLines(headersListFile.getPath(), 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 (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())); } }