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

Java tutorial

Introduction

Here is the source code for com.facebook.buck.cxx.CxxPreprocessStep.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.step.ExecutionContext;
import com.facebook.buck.step.Step;
import com.facebook.buck.util.Escaper;
import com.facebook.buck.util.FunctionLineProcessorThread;
import com.facebook.buck.util.MoreIterables;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Function;
import com.google.common.base.Functions;
import com.google.common.base.Joiner;
import com.google.common.base.Optional;
import com.google.common.collect.FluentIterable;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Iterables;

import java.io.ByteArrayOutputStream;
import java.io.InterruptedIOException;
import java.io.OutputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * A step that preprocesses C/C++ sources.
 */
public class CxxPreprocessStep implements Step {

    private final Path preprocessor;
    private final ImmutableList<String> flags;
    private final Path output;
    private final Path input;
    private final ImmutableList<Path> includes;
    private final ImmutableList<Path> systemIncludes;
    private final ImmutableMap<Path, Path> replacementPaths;
    private final Optional<DebugPathSanitizer> sanitizer;

    public CxxPreprocessStep(Path preprocessor, ImmutableList<String> flags, Path output, Path input,
            ImmutableList<Path> includes, ImmutableList<Path> systemIncludes,
            ImmutableMap<Path, Path> replacementPaths, Optional<DebugPathSanitizer> sanitizer) {
        this.preprocessor = preprocessor;
        this.flags = flags;
        this.output = output;
        this.input = input;
        this.includes = includes;
        this.systemIncludes = systemIncludes;
        this.replacementPaths = replacementPaths;
        this.sanitizer = sanitizer;
    }

    @Override
    public String getShortName() {
        return "c++ preprocess";
    }

    @VisibleForTesting
    protected ImmutableList<String> getCommand() {
        return ImmutableList.<String>builder().add(preprocessor.toString()).add("-E").addAll(flags)
                .addAll(MoreIterables.zipAndConcat(Iterables.cycle("-I"),
                        Iterables.transform(includes, Functions.toStringFunction())))
                .addAll(MoreIterables.zipAndConcat(Iterables.cycle("-isystem"),
                        Iterables.transform(systemIncludes, Functions.toStringFunction())))
                .add(input.toString()).build();
    }

    @VisibleForTesting
    protected static Function<String, String> createOutputLineProcessor(final Path workingDir,
            final ImmutableMap<Path, Path> replacementPaths, final Optional<DebugPathSanitizer> sanitizer) {

        return new Function<String, String>() {

            private final Pattern lineMarkers = Pattern.compile("^# (?<num>\\d+) \"(?<path>[^\"]+)\"(?<rest>.*)?$");

            @Override
            public String apply(String line) {
                if (line.startsWith("# ")) {
                    Matcher m = lineMarkers.matcher(line);
                    if (m.find()) {
                        String originalPath = m.group("path");
                        String replacementPath = originalPath;

                        replacementPath = Optional.fromNullable(replacementPaths.get(Paths.get(replacementPath)))
                                .transform(Functions.toStringFunction()).or(replacementPath);

                        if (sanitizer.isPresent()) {
                            replacementPath = sanitizer.get().sanitize(Optional.of(workingDir), replacementPath);
                        }

                        if (!originalPath.equals(replacementPath)) {
                            String num = m.group("num");
                            String rest = m.group("rest");
                            return "# " + num + " \"" + replacementPath + "\"" + rest;
                        }
                    }
                }
                return line;
            }

        };
    }

    @VisibleForTesting
    protected Function<String, String> createErrorLineProcessor() {
        return CxxDescriptionEnhancer.createErrorMessagePathProcessor(new Function<String, String>() {
            @Override
            public String apply(String path) {
                Path replacement = replacementPaths.get(Paths.get(path));
                return replacement != null ? replacement.toString() : path;
            }
        });
    }

    @Override
    @SuppressWarnings("PMD.EmptyTryBlock")
    public int execute(ExecutionContext context) throws InterruptedException {
        ProcessBuilder builder = new ProcessBuilder();
        builder.command(getCommand());
        builder.directory(context.getProjectDirectoryRoot().toAbsolutePath().toFile());
        builder.redirectOutput(ProcessBuilder.Redirect.PIPE);
        builder.redirectError(ProcessBuilder.Redirect.PIPE);

        Path outputPath = context.getProjectFilesystem().resolve(this.output);
        Path outputTempPath = context.getProjectFilesystem().resolve(this.output + ".tmp");

        try {

            // Start the process.
            Process process = builder.start();

            // We buffer error messages in memory, as these are typically small.
            ByteArrayOutputStream error = new ByteArrayOutputStream();

            // Open the temp file to write the intermediate output to and also fire up managed threads
            // to process the stdout and stderr lines from the preprocess command.
            try (OutputStream output = Files.newOutputStream(outputTempPath);
                    FunctionLineProcessorThread outputProcessor = new FunctionLineProcessorThread(
                            process.getInputStream(), output,
                            createOutputLineProcessor(context.getProjectDirectoryRoot(), replacementPaths,
                                    sanitizer));
                    FunctionLineProcessorThread errorProcessor = new FunctionLineProcessorThread(
                            process.getErrorStream(), error, createErrorLineProcessor())) {
                outputProcessor.start();
                errorProcessor.start();
            }

            // Wait for the process to finish, and grab it's exit code.
            int exitCode = process.waitFor();

            // If the process finished successfully, move the preprocessed output into it's final place.
            if (exitCode == 0) {
                Files.move(outputTempPath, outputPath, StandardCopyOption.REPLACE_EXISTING,
                        StandardCopyOption.ATOMIC_MOVE);
            }

            // If we generated any error output, print that to the console.
            String err = new String(error.toByteArray());
            if (!err.isEmpty()) {
                context.getConsole().printErrorText(err);
            }

            return exitCode;

        } catch (InterruptedException | InterruptedIOException e) {
            throw new InterruptedException();

        } catch (Exception e) {
            context.getConsole().printBuildFailureWithStacktrace(e);
            return 1;
        }
    }

    @Override
    public String getDescription(ExecutionContext context) {
        return Joiner.on(' ').join(FluentIterable.from(getCommand()).transform(Escaper.BASH_ESCAPER));
    }

}