/* * Copyright 2014-present Facebook, Inc. * * 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.facebook.buck.cxx; import com.facebook.buck.model.BuildTarget; import com.facebook.buck.rules.ExplicitBuildTargetSourcePath; import com.facebook.buck.rules.RuleKeyAppendable; import com.facebook.buck.rules.RuleKeyObjectSink; import com.facebook.buck.rules.SourcePath; import com.facebook.buck.rules.SourcePathResolver; import com.facebook.buck.rules.SymlinkTree; import com.facebook.buck.rules.args.RuleKeyAppendableFunction; import com.facebook.buck.rules.coercer.FrameworkPath; import com.facebook.buck.util.HumanReadableException; import com.facebook.buck.util.MoreSuppliers; import com.google.common.base.Charsets; import com.google.common.base.Preconditions; import com.google.common.base.Supplier; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSortedSet; import com.google.common.collect.Iterables; import com.google.common.hash.Hasher; import com.google.common.hash.Hashing; import java.nio.file.Path; import java.util.HashMap; import java.util.Map; import java.util.Optional; import java.util.function.Predicate; /** Helper class for handling preprocessing related tasks of a cxx compilation rule. */ final class PreprocessorDelegate implements RuleKeyAppendable { // Fields that are added to rule key as is. private final Preprocessor preprocessor; private final RuleKeyAppendableFunction<FrameworkPath, Path> frameworkPathSearchPathFunction; private final HeaderVerification headerVerification; // Fields that added to the rule key with some processing. private final PreprocessorFlags preprocessorFlags; // Fields that are not added to the rule key. private final DebugPathSanitizer sanitizer; private final Path workingDir; private final SourcePathResolver resolver; private final Optional<SymlinkTree> sandbox; /** * If present, these paths will be added first (prior to the current rule's list of paths) when * building the list of compiler flags, in {@link #getFlagsWithSearchPaths(Optional)}. */ private final Optional<CxxIncludePaths> leadingIncludePaths; private final PathShortener minLengthPathRepresentation; private final Supplier<HeaderPathNormalizer> headerPathNormalizer = MoreSuppliers.weakMemoize( new Supplier<HeaderPathNormalizer>() { @Override public HeaderPathNormalizer get() { HeaderPathNormalizer.Builder builder = new HeaderPathNormalizer.Builder(resolver); for (CxxHeaders include : preprocessorFlags.getIncludes()) { include.addToHeaderPathNormalizer(builder); } for (FrameworkPath frameworkPath : preprocessorFlags.getFrameworkPaths()) { frameworkPath.getSourcePath().ifPresent(builder::addHeaderDir); } if (preprocessorFlags.getPrefixHeader().isPresent()) { SourcePath headerPath = preprocessorFlags.getPrefixHeader().get(); builder.addPrefixHeader(headerPath); } if (sandbox.isPresent()) { ExplicitBuildTargetSourcePath root = new ExplicitBuildTargetSourcePath( sandbox.get().getBuildTarget(), sandbox.get().getRoot()); builder.addSymlinkTree(root, sandbox.get().getLinks()); } return builder.build(); } }); public PreprocessorDelegate( SourcePathResolver resolver, DebugPathSanitizer sanitizer, HeaderVerification headerVerification, Path workingDir, Preprocessor preprocessor, PreprocessorFlags preprocessorFlags, RuleKeyAppendableFunction<FrameworkPath, Path> frameworkPathSearchPathFunction, Optional<SymlinkTree> sandbox, Optional<CxxIncludePaths> leadingIncludePaths) throws ConflictingHeadersException { this.preprocessor = preprocessor; this.preprocessorFlags = preprocessorFlags; this.sanitizer = sanitizer; this.headerVerification = headerVerification; this.workingDir = workingDir; this.minLengthPathRepresentation = PathShortener.byRelativizingToWorkingDir(workingDir); this.resolver = resolver; this.frameworkPathSearchPathFunction = frameworkPathSearchPathFunction; this.sandbox = sandbox; this.leadingIncludePaths = leadingIncludePaths; checkForConflictingHeaders(); } public PreprocessorDelegate withLeadingIncludePaths(CxxIncludePaths leadingIncludePaths) throws ConflictingHeadersException { return new PreprocessorDelegate( this.resolver, this.sanitizer, this.headerVerification, this.workingDir, this.preprocessor, this.preprocessorFlags, this.frameworkPathSearchPathFunction, this.sandbox, Optional.of(leadingIncludePaths)); } public Preprocessor getPreprocessor() { return preprocessor; } @Override public void appendToRuleKey(RuleKeyObjectSink sink) { sink.setReflectively("preprocessor", preprocessor); sink.setReflectively("frameworkPathSearchPathFunction", frameworkPathSearchPathFunction); sink.setReflectively("headerVerification", headerVerification); preprocessorFlags.appendToRuleKey(sink, sanitizer); } public HeaderPathNormalizer getHeaderPathNormalizer() { return headerPathNormalizer.get(); } /** * Get the command for standalone preprocessor calls. * * @param compilerFlags flags to append. */ public ImmutableList<String> getCommand( CxxToolFlags compilerFlags, Optional<CxxPrecompiledHeader> pch) { return ImmutableList.<String>builder() .addAll(getCommandPrefix()) .addAll(getArguments(compilerFlags, pch)) .build(); } public ImmutableList<String> getCommandPrefix() { return preprocessor.getCommandPrefix(resolver); } public ImmutableList<String> getArguments( CxxToolFlags compilerFlags, Optional<CxxPrecompiledHeader> pch) { return ImmutableList.copyOf( CxxToolFlags.concat(getFlagsWithSearchPaths(pch), compilerFlags).getAllFlags()); } public ImmutableMap<String, String> getEnvironment() { return preprocessor.getEnvironment(resolver); } public CxxToolFlags getFlagsWithSearchPaths(Optional<CxxPrecompiledHeader> pch) { CxxToolFlags leadingFlags; if (leadingIncludePaths.isPresent()) { leadingFlags = leadingIncludePaths .get() .toToolFlags( resolver, minLengthPathRepresentation, frameworkPathSearchPathFunction, preprocessor); } else { leadingFlags = CxxToolFlags.of(); } return CxxToolFlags.concat( leadingFlags, preprocessorFlags.toToolFlags( resolver, minLengthPathRepresentation, frameworkPathSearchPathFunction, preprocessor, pch)); } /** * Get all the preprocessor's include paths. * * @see PreprocessorFlags#getCxxIncludePaths() */ public CxxIncludePaths getCxxIncludePaths() { return preprocessorFlags.getCxxIncludePaths(); } public CxxToolFlags getNonIncludePathFlags(Optional<CxxPrecompiledHeader> pch) { return preprocessorFlags.getNonIncludePathFlags(resolver, pch, preprocessor); } /** * Build a {@link CxxToolFlags} representing our include paths (local, system, iquote, framework). * Does not include {@link #leadingIncludePaths}. */ public CxxToolFlags getIncludePathFlags() { return preprocessorFlags.getIncludePathFlags( resolver, minLengthPathRepresentation, frameworkPathSearchPathFunction, preprocessor); } private void checkForConflictingHeaders() throws ConflictingHeadersException { Map<Path, SourcePath> headers = new HashMap<>(); for (CxxHeaders cxxHeaders : this.preprocessorFlags.getIncludes()) { if (cxxHeaders instanceof CxxSymlinkTreeHeaders) { CxxSymlinkTreeHeaders symlinkTreeHeaders = (CxxSymlinkTreeHeaders) cxxHeaders; for (Map.Entry<Path, SourcePath> entry : symlinkTreeHeaders.getNameToPathMap().entrySet()) { SourcePath original = headers.put(entry.getKey(), entry.getValue()); if (original != null && !original.equals(entry.getValue())) { throw new ConflictingHeadersException(entry.getKey(), original, entry.getValue()); } } } } } /** @see com.facebook.buck.rules.keys.SupportsDependencyFileRuleKey */ public ImmutableList<SourcePath> getInputsAfterBuildingLocally(Iterable<Path> depFileLines) { ImmutableList.Builder<SourcePath> inputs = ImmutableList.builder(); // Add inputs that we always use. inputs.addAll(preprocessor.getInputs()); // Prefix header is not represented in the dep file, so should be added manually. if (preprocessorFlags.getPrefixHeader().isPresent()) { inputs.add(preprocessorFlags.getPrefixHeader().get()); } // Add any header/include inputs that our dependency file said we used. // // TODO(#9117006): We need to find out which `SourcePath` each line in the dep file refers to. // Since we force our compilation process to refer to headers using relative paths, // which results in dep files containing relative paths, we can't always get this 100% // correct (e.g. there may be two `SourcePath` includes with the same relative path, but // coming from different cells). Favor correctness in this case and just add *all* // `SourcePath`s that have relative paths matching those specific in the dep file. HeaderPathNormalizer headerPathNormalizer = getHeaderPathNormalizer(); for (Path absolutePath : depFileLines) { Preconditions.checkState(absolutePath.isAbsolute()); inputs.add(headerPathNormalizer.getSourcePathForAbsolutePath(absolutePath)); } return inputs.build(); } public Predicate<SourcePath> getCoveredByDepFilePredicate() { // TODO(jkeljo): I didn't know how to implement this, and didn't have time to figure it out. return (SourcePath path) -> true; } public Optional<ImmutableList<String>> getFlagsForColorDiagnostics() { return preprocessor.getFlagsForColorDiagnostics(); } public HeaderVerification getHeaderVerification() { return headerVerification; } public Optional<SourcePath> getPrefixHeader() { return preprocessorFlags.getPrefixHeader(); } /** * Generate a digest of the compiler flags. * * <p>Generated PCH files can only be used when compiling with similar compiler flags. This * guarantees the uniqueness of the generated file. * * <p>Note: when building the hash for identifying the PCH itself, just pass {@code * Optional.empty()}. */ public String hashCommand(CxxToolFlags flags, Optional<CxxPrecompiledHeader> pch) { return hashCommand(getCommand(flags, pch)); } public String hashCommand(ImmutableList<String> flags) { Hasher hasher = Hashing.murmur3_128().newHasher(); String workingDirString = workingDir.toString(); // Skips the executable argument (the first one) as that is not sanitized. for (String part : sanitizer.sanitizeFlags(Iterables.skip(flags, 1))) { // TODO(#10251354): find a better way of dealing with getting a project dir normalized hash if (part.startsWith(workingDirString)) { part = "<WORKINGDIR>" + part.substring(workingDirString.length()); } hasher.putString(part, Charsets.UTF_8); hasher.putBoolean(false); // separator } return hasher.hash().toString(); } @SuppressWarnings("serial") public static class ConflictingHeadersException extends Exception { public ConflictingHeadersException(Path key, SourcePath value1, SourcePath value2) { super(String.format("'%s' maps to both %s.", key, ImmutableSortedSet.of(value1, value2))); } public HumanReadableException getHumanReadableExceptionForBuildTarget(BuildTarget buildTarget) { return new HumanReadableException( this, "Target '%s' uses conflicting header file mappings. %s", buildTarget, getMessage()); } } public PreprocessorFlags getPreprocessorFlags() { return preprocessorFlags; } }