org.daisy.common.shell.BinaryFinder Maven / Gradle / Ivy
The newest version!
package org.daisy.common.shell;
import java.io.BufferedReader;
import java.io.File;
import java.io.InputStreamReader;
import java.io.IOException;
import java.util.ArrayList;
import static java.util.Arrays.asList;
import static java.util.Collections.emptyList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.Optional;
import java.util.Scanner;
import java.util.Set;
import com.google.common.collect.AbstractIterator;
import static com.google.common.collect.Iterables.concat;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class BinaryFinder {
private static final Logger logger = LoggerFactory.getLogger(BinaryFinder.class);
/**
* Look for a given executable in the PATH environment variable.
*
* @return the absolute path of the executable if it exists, an absent
* optional otherwise.
*/
public static Optional find(String executableName) {
String os = System.getProperty("os.name");
return find(executableName,
(os != null && os.startsWith("Windows")) ? winExtensions : nixExtensions,
getPath());
}
/**
* Test whether a path points to a file that is executable, and return the {@link File} if it
* is. Otherwise log a warning message.
*/
public static Optional get(String executablePath) {
return get(new File(executablePath));
}
private static Optional get(File executableFile) {
try {
if (executableFile.canExecute()) {
return Optional.of(executableFile);
}
} catch (SecurityException e) {
}
if (!executableFile.exists()) {
logger.warn("File does not exist: " + executableFile);
} else if (executableFile.isDirectory()) {
logger.warn("Expected a file, but got a directory: " + executableFile);
} else if (!executableFile.isFile()) {
logger.warn("Not a file: " + executableFile);
} else {
logger.warn("File is not executable: " + executableFile);
}
return Optional.empty();
}
private static Iterable path;
private static Iterable getPath() {
if (path == null) {
List> paths = new ArrayList<>();
paths.add(pathFromEnv());
String os = System.getProperty("os.name");
if (os == null || !os.startsWith("Windows")) {
paths.add(pathFromPathHelper());
paths.add(pathFromShell());
paths.add(asList(nixUltimateFallbackPath));
}
path = memoize(removeDuplicates(concat(paths)));
}
return path;
}
static Optional find(String executableName, String[] extensions, Iterable path) {
for (String ext : extensions) {
String fullname = executableName + ext;
logger.debug("looking for " + fullname + " ...");
for (String pathDir : path) {
logger.debug("... in " + pathDir);
File file = new File(pathDir, fullname);
if (file.isFile()) {
Optional executableFile = BinaryFinder.get(file);
if (executableFile.isPresent()) {
logger.debug("found: " + file.getAbsolutePath());
return Optional.of(executableFile.get().getAbsolutePath());
}
}
}
}
return Optional.empty();
}
private static final String[] winExtensions = {
".exe", ".bat", ".cmd", ".bin", ""
};
private static final String[] nixExtensions = {
"", ".run", ".bin", ".sh"
};
private static final String[] nixUltimateFallbackPath = {
"/usr/bin", "/bin", "/usr/sbin", "/sbin", "/usr/local/bin"
};
static Iterable pathFromString(String path, String pathSeparator) {
if (path == null || pathSeparator == null)
return emptyList;
return asList(path.split(pathSeparator));
}
/*
* When Pipeline is launched via a terminal window, the PATH
* environment variable is determined by what it was set to in the
* shell.
*
* When Pipeline is launched as a Mac OS app, the PATH is
* determined by what it was set to in launchd. Variables may be
* set via launchctl:
*
* launchctl setenv PATH ...
*
* With up to and including Mac OS 10.9 (Mavericks), the
* /etc/launchd.conf file may be used to persist the setting
* across reboots. In 10.10 (Yosemite) and later a launch agent
* (~/Library/LaunchAgents/foo.plist) must be used for this.
*
* With 10.7 (Lion) and older you may set environment variables in
* ~/.MacOSX/environment.plist.
*/
static Iterable pathFromEnv() {
return pathFromString(System.getenv("PATH"), File.pathSeparator);
}
/*
* Because often environment variables are set by the shell and
* people may not realize that in general these variables are not
* available in Mac OS applications, we try to make them available
* anyway.
*
* /usr/libexec/path_helper is a utility intended to be used by
* shells for constructing the PATH environment variable. It reads
* from the files /etc/paths and /etc/paths.d/*.
*/
static Iterable pathFromPathHelper() {
return new PathFromPathHelper();
}
static class PathFromPathHelper implements Iterable {
public Iterator iterator() {
String pathHelperExec = "/usr/libexec/path_helper";
if (new File(pathHelperExec).isFile()) {
logger.debug("invoking: " + pathHelperExec + " -s");
ProcessBuilder builder = new ProcessBuilder(pathHelperExec, "-s");
try {
Process process = builder.start();
process.waitFor();
BufferedReader br = new BufferedReader(new InputStreamReader(process.getInputStream()));
try {
String line;
while ((line = br.readLine()) != null) {
logger.debug(line);
if (line.startsWith("PATH=\"") && line.endsWith("\"; export PATH;"))
return pathFromString(line.substring(6, line.length() - 15), File.pathSeparator).iterator();
}
} finally {
br.close();
}
} catch(IOException e) {
logger.debug("failed: " + e);
} catch(InterruptedException e) {
logger.debug("failed: " + e);
}
}
return emptyList.iterator();
}
}
/*
* The PATH may be set in shell startup files such as
* /etc/profile, ~/.profile, /etc/bash.bashrc, ~/.bash_profile,
* ~/.bash_login, ~/.bashrc, /etc/csh.login and /etc/zshenv. Some
* of these files invoke /usr/libexec/path_helper but they can
* also set the PATH in other ways.
*/
static Iterable pathFromShell() {
return new PathFromShell();
}
static class PathFromShell implements Iterable {
public Iterator iterator() {
String shellExec = System.getenv("SHELL");
if (shellExec == null)
shellExec = "/bin/sh";
if (new File(shellExec).isFile()) {
logger.debug("invoking: " + shellExec + " -c \"echo $PATH\"");
ProcessBuilder builder = new ProcessBuilder(shellExec, "-c", "echo $PATH");
try {
Process process = builder.start();
process.waitFor();
Scanner s = new Scanner(process.getInputStream()).useDelimiter("\\A");
try {
if (s.hasNext()) {
String output = s.next();
logger.debug(output);
return pathFromString(output, File.pathSeparator).iterator();
}
} finally {
s.close();
}
} catch(IOException e) {
logger.debug("failed: " + e);
} catch(InterruptedException e) {
logger.debug("failed: " + e);
}
}
return emptyList.iterator();
}
}
private final static Iterable emptyList = emptyList();
static Iterable removeDuplicates(final Iterable iterable) {
return new Iterable() {
public Iterator iterator() {
return new AbstractIterator() {
Set previous;
Iterator iterator;
protected T computeNext() {
if (iterator == null)
iterator = iterable.iterator();
while (iterator.hasNext()) {
T next = iterator.next();
if (previous == null)
previous = new HashSet();
else if (previous.contains(next))
continue;
previous.add(next);
return next;
}
return endOfData();
}
};
}
};
}
static Iterable memoize(final Iterable iterable) {
return new Iterable() {
final ArrayList cache = new ArrayList();
public Iterator iterator() {
return new Iterator() {
Iterator iterator;
int index = 0;
public boolean hasNext() {
synchronized(cache) {
if (index < cache.size())
return true;
if (iterator == null)
iterator = iterable.iterator();
return iterator.hasNext();
}
}
public T next() throws NoSuchElementException {
synchronized(cache) {
if (index < cache.size())
return cache.get(index++);
if (iterator == null)
iterator = iterable.iterator();
T next = iterator.next();
cache.add(next);
index++;
return next;
}
}
public void remove() {
throw new UnsupportedOperationException();
}
};
}
};
}
}