com.facebook.buck.cxx.PreprocessorDelegate.java Source code

Java tutorial

Introduction

Here is the source code for com.facebook.buck.cxx.PreprocessorDelegate.java

Source

/*
 * 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.BuildTargetSourcePath;
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.ImmutableSet;
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.nio.file.Paths;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;

/**
 * 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;

    // 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 HeaderVerification headerVerification;
    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,
                            minLengthPathRepresentation);
                    for (CxxHeaders include : preprocessorFlags.getIncludes()) {
                        include.addToHeaderPathNormalizer(builder);
                    }
                    if (preprocessorFlags.getPrefixHeader().isPresent()) {
                        SourcePath headerPath = preprocessorFlags.getPrefixHeader().get();
                        builder.addPrefixHeader(headerPath);
                    }
                    if (sandbox.isPresent()) {
                        BuildTargetSourcePath root = new BuildTargetSourcePath(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();
    }

    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<String> 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 (String line : depFileLines) {
            Path absolutePath = Paths.get(line);
            Preconditions.checkState(absolutePath.isAbsolute());
            inputs.add(headerPathNormalizer.getSourcePathForAbsolutePath(Paths.get(line)));
        }

        return inputs.build();
    }

    public Optional<ImmutableSet<SourcePath>> getPossibleInputSourcePaths() {
        // TODO(jkeljo): I didn't know how to implement this, and didn't have time to figure it out.
        return Optional.empty();
    }

    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.
     *
     * Generated PCH files can only be used when compiling with similar compiler flags. This
     * guarantees the uniqueness of the generated file.
     *
     * 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;
    }

}