com.facebook.buck.io.ExecutableFinder.java Source code

Java tutorial

Introduction

Here is the source code for com.facebook.buck.io.ExecutableFinder.java

Source

/*
 * Copyright 2015-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.io;

import static java.io.File.pathSeparator;

import com.facebook.buck.log.Logger;
import com.facebook.buck.util.HumanReadableException;
import com.facebook.buck.util.environment.EnvironmentFilter;
import com.facebook.buck.util.environment.Platform;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Splitter;
import com.google.common.collect.ImmutableCollection;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;

import java.io.IOException;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Optional;
import java.util.stream.StreamSupport;

/**
 * Given the name of an executable, search a set of (possibly platform-specific) known locations for
 * that executable.
 */
public class ExecutableFinder {

    private static final Logger LOG = Logger.get(ExecutableFinder.class);
    private static final ImmutableSet<String> DEFAULT_WINDOWS_EXTENSIONS = ImmutableSet.of(".bat", ".cmd", ".com",
            ".cpl", ".exe", ".js", ".jse", ".msc", ".vbs", ".wsf", ".wsh");

    private final Platform platform;

    public ExecutableFinder() {
        this(Platform.detect());
    }

    @VisibleForTesting
    public ExecutableFinder(Platform platform) {
        this.platform = platform;
    }

    public Path getExecutable(Path suggestedExecutable, ImmutableMap<String, String> env) {
        Optional<Path> exe = getOptionalExecutable(suggestedExecutable, env);
        if (!exe.isPresent()) {
            throw new HumanReadableException(String.format(
                    "Unable to locate %s on PATH, or it's not marked as being executable", suggestedExecutable));
        }
        return exe.get();
    }

    public Optional<Path> getOptionalExecutable(Path suggestedExecutable, ImmutableMap<String, String> env) {
        env = EnvironmentFilter.filteredEnvironment(env, platform);

        return getOptionalExecutable(suggestedExecutable, getPaths(env), getExecutableSuffixes(env));
    }

    public Optional<Path> getOptionalExecutable(Path suggestedExecutable, Path basePath) {
        return getOptionalExecutable(suggestedExecutable, ImmutableSet.of(basePath),
                getExecutableSuffixes(ImmutableMap.of()));
    }

    public Optional<Path> getOptionalExecutable(Path suggestedExecutable, ImmutableCollection<Path> path,
            ImmutableCollection<String> fileSuffixes) {

        // Fast path out of here.
        if (isExecutable(suggestedExecutable)) {
            return Optional.of(suggestedExecutable);
        }

        Optional<Path> executable = FileFinder.getOptionalFile(FileFinder.combine(/* prefixes */ null,
                suggestedExecutable.toString(), ImmutableSet.copyOf(fileSuffixes)), path,
                ExecutableFinder::isExecutable);
        LOG.debug("Executable '%s' mapped to '%s'", suggestedExecutable, executable);

        return executable;
    }

    private static boolean isExecutable(Path exe) {
        if (!Files.exists(exe)) {
            return false;
        }

        if (Files.isSymbolicLink(exe)) {
            try {
                Path target = Files.readSymbolicLink(exe);
                return isExecutable(exe.resolveSibling(target).normalize());
            } catch (IOException | SecurityException e) { // NOPMD
            }
        }

        if (Files.isDirectory(exe)) {
            LOG.debug("Found potential executable, but is a directory: %s", exe);
            return false;
        }

        if (!Files.isExecutable(exe) && !Files.isSymbolicLink(exe)) {
            LOG.debug("Found potential executable, but not actually executable: %s", exe);
            return false;
        }

        return true;
    }

    private ImmutableSet<Path> getPaths(ImmutableMap<String, String> env) {
        ImmutableSet.Builder<Path> paths = ImmutableSet.builder();

        // Add the empty path so that when we iterate over it, we can check for the suffixed version of
        // a given path, be it absolute or not.
        paths.add(Paths.get(""));

        String pathEnv = env.get("PATH");
        if (pathEnv != null) {
            pathEnv = pathEnv.trim();
            paths.addAll(StreamSupport
                    .stream(Splitter.on(pathSeparator).omitEmptyStrings().split(pathEnv).spliterator(), false)
                    .map(Paths::get).iterator());
        }

        if (platform == Platform.MACOS) {
            Path osXPaths = Paths.get("/etc/paths");
            if (Files.exists(osXPaths)) {
                try {
                    paths.addAll(Files.readAllLines(osXPaths, Charset.defaultCharset()).stream().map(Paths::get)
                            .iterator());
                } catch (IOException e) {
                    LOG.warn("Unable to read mac-specific paths. Skipping");
                }
            }
        }

        return paths.build();
    }

    private ImmutableSet<String> getExecutableSuffixes(ImmutableMap<String, String> env) {
        if (platform == Platform.WINDOWS) {
            String pathext = env.get("PATHEXT");
            if (pathext == null) {
                return DEFAULT_WINDOWS_EXTENSIONS;
            }
            return ImmutableSet.<String>builder().addAll(Splitter.on(";").omitEmptyStrings().split(pathext))
                    .build();
        }
        return ImmutableSet.of("");
    }

    public Optional<Path> getOptionalToolPath(String tool, ImmutableList<Path> toolSearchPaths) {
        return getOptionalExecutable(Paths.get(tool), toolSearchPaths, ImmutableSet.of());
    }
}