blob: 9ffdc420673b77f48ed2c6db5b4d5acd330f7b1c [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2015 QNX Software Systems and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*******************************************************************************/
package org.eclipse.cdt.build.gcc.core;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.URI;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.eclipse.cdt.build.gcc.core.internal.Activator;
import org.eclipse.cdt.core.CCorePlugin;
import org.eclipse.cdt.core.build.IToolChain;
import org.eclipse.cdt.core.build.IToolChainProvider;
import org.eclipse.cdt.core.envvar.EnvironmentVariable;
import org.eclipse.cdt.core.envvar.IEnvironmentVariable;
import org.eclipse.cdt.core.parser.ExtendedScannerInfo;
import org.eclipse.cdt.core.parser.IExtendedScannerInfo;
import org.eclipse.core.resources.IBuildConfiguration;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IWorkspaceRoot;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.PlatformObject;
/**
* The GCC toolchain. This is the base class for all GCC toolchains. It
* represents GCC as found on the user's PATH. It can be overriden to change
* environment variable settings.
*/
public class GCCToolChain extends PlatformObject implements IToolChain {
private final IToolChainProvider provider;
private final String id;
private final String version;
private final String name;
private final Path[] path;
private final String prefix;
private final IEnvironmentVariable pathVar;
private final IEnvironmentVariable[] envVars;
private final Map<String, String> properties = new HashMap<>();
protected String[] compileCommands;
public GCCToolChain(IToolChainProvider provider, String id, String version) {
this(provider, id, version, null, null);
}
public GCCToolChain(IToolChainProvider provider, String id, String version, Path[] path) {
this(provider, id, version, path, null);
}
public GCCToolChain(IToolChainProvider provider, String id, String version, Path[] path, String prefix) {
this.provider = provider;
this.id = id;
this.version = version;
this.name = id + " - " + version; //$NON-NLS-1$
this.path = path;
this.prefix = prefix;
if (path != null) {
StringBuilder pathString = new StringBuilder();
for (int i = 0; i < path.length; ++i) {
pathString.append(path[i].toString());
if (i < path.length - 1) {
pathString.append(File.pathSeparator);
}
}
pathVar = new EnvironmentVariable("PATH", pathString.toString(), IEnvironmentVariable.ENVVAR_PREPEND, //$NON-NLS-1$
File.pathSeparator);
envVars = new IEnvironmentVariable[] { pathVar };
} else {
pathVar = null;
envVars = new IEnvironmentVariable[0];
}
}
@Override
public IToolChainProvider getProvider() {
return provider;
}
@Override
public String getId() {
return id;
}
@Override
public String getVersion() {
return version;
}
@Override
public String getName() {
return name;
}
@Override
public String getProperty(String key) {
String value = properties.get(key);
if (value != null) {
return value;
}
// By default, we're a local GCC
switch (key) {
case ATTR_OS:
return Platform.getOS();
case ATTR_ARCH:
return Platform.getOSArch();
}
return null;
}
@Override
public void setProperty(String key, String value) {
properties.put(key, value);
}
@Override
public String getBinaryParserId() {
// Assume local builds
// TODO be smarter and use the id which should be the target
switch (Platform.getOS()) {
case Platform.OS_WIN32:
return CCorePlugin.PLUGIN_ID + ".PE"; //$NON-NLS-1$
case Platform.OS_MACOSX:
return CCorePlugin.PLUGIN_ID + ".MachO64"; //$NON-NLS-1$
default:
return CCorePlugin.PLUGIN_ID + ".ELF"; //$NON-NLS-1$
}
}
protected void addDiscoveryOptions(List<String> command) {
command.add("-E"); //$NON-NLS-1$
command.add("-P"); //$NON-NLS-1$
command.add("-v"); //$NON-NLS-1$
command.add("-dD"); //$NON-NLS-1$
}
@Override
public IExtendedScannerInfo getScannerInfo(IBuildConfiguration buildConfig, Path command, String[] args,
IExtendedScannerInfo baseScannerInfo, IResource resource, URI buildDirectoryURI) {
try {
Path buildDirectory = Paths.get(buildDirectoryURI);
List<String> commandLine = new ArrayList<>();
if (command.isAbsolute()) {
commandLine.add(command.toString());
} else {
commandLine.add(getCommandPath(command).toString());
}
if (baseScannerInfo != null && baseScannerInfo.getIncludePaths() != null) {
for (String includePath : baseScannerInfo.getIncludePaths()) {
commandLine.add("-I" + includePath); //$NON-NLS-1$
}
}
addDiscoveryOptions(commandLine);
commandLine.addAll(Arrays.asList(args));
// Change output to stdout
boolean haveOut = false;
for (int i = 0; i < commandLine.size() - 1; ++i) {
if (commandLine.get(i).equals("-o")) { //$NON-NLS-1$
commandLine.set(i + 1, "-"); //$NON-NLS-1$
haveOut = true;
break;
}
}
if (!haveOut) {
commandLine.add("-o"); //$NON-NLS-1$
commandLine.add("-"); //$NON-NLS-1$
}
// Change source file to a tmp file (needs to be empty)
Path tmpFile = null;
for (int i = 1; i < commandLine.size(); ++i) {
if (!commandLine.get(i).startsWith("-")) { //$NON-NLS-1$
// TODO optimize by dealing with multi arg options like -o
Path filePath = buildDirectory.resolve(commandLine.get(i));
IFile[] files = ResourcesPlugin.getWorkspace().getRoot().findFilesForLocationURI(filePath.toUri());
if (files.length > 0) {
// replace it with a temp file
Path parentPath = filePath.getParent();
String extension = files[0].getFileExtension();
if (extension == null) {
// Not sure if this is a reasonable choice when
// there's
// no extension
extension = ".cpp"; //$NON-NLS-1$
} else {
extension = '.' + extension;
}
tmpFile = Files.createTempFile(parentPath, ".sc", extension); //$NON-NLS-1$
commandLine.set(i, tmpFile.toString());
}
}
}
if (tmpFile == null) {
// Have to assume there wasn't a source file. Add one in the
// resource's container
IPath parentPath = resource instanceof IFile ? resource.getParent().getLocation()
: resource.getLocation();
tmpFile = Files.createTempFile(parentPath.toFile().toPath(), ".sc", ".cpp"); //$NON-NLS-1$ //$NON-NLS-2$
commandLine.add(tmpFile.toString());
}
Files.createDirectories(buildDirectory);
// Startup the command
ProcessBuilder processBuilder = new ProcessBuilder(commandLine).directory(buildDirectory.toFile())
.redirectErrorStream(true);
CCorePlugin.getDefault().getBuildEnvironmentManager().setEnvironment(processBuilder.environment(),
buildConfig, true);
Process process = processBuilder.start();
// Scan for the scanner info
Map<String, String> symbols = new HashMap<>();
List<String> includePath = new ArrayList<>();
Pattern definePattern = Pattern.compile("#define (.*)\\s(.*)"); //$NON-NLS-1$
boolean inIncludePaths = false;
try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()))) {
for (String line = reader.readLine(); line != null; line = reader.readLine()) {
if (inIncludePaths) {
if (line.equals("End of search list.")) { //$NON-NLS-1$
inIncludePaths = false;
} else {
includePath.add(line.trim());
}
} else if (line.startsWith("#define ")) { //$NON-NLS-1$
Matcher matcher = definePattern.matcher(line);
if (matcher.matches()) {
symbols.put(matcher.group(1), matcher.group(2));
}
} else if (line.equals("#include <...> search starts here:")) { //$NON-NLS-1$
inIncludePaths = true;
}
}
}
try {
process.waitFor();
} catch (InterruptedException e) {
Activator.log(e);
}
Files.delete(tmpFile);
return new ExtendedScannerInfo(symbols, includePath.toArray(new String[includePath.size()]));
} catch (IOException e) {
Activator.log(e);
return null;
}
}
@Override
public String[] getErrorParserIds() {
return new String[] { "org.eclipse.cdt.core.GCCErrorParser", //$NON-NLS-1$
"org.eclipse.cdt.core.GASErrorParser", //$NON-NLS-1$
"org.eclipse.cdt.core.GLDErrorParser", //$NON-NLS-1$
"org.eclipse.cdt.core.GmakeErrorParser", //$NON-NLS-1$
"org.eclipse.cdt.core.CWDLocator" //$NON-NLS-1$
};
}
@Override
public IEnvironmentVariable getVariable(String name) {
if (path != null && name.equals("PATH")) { //$NON-NLS-1$
return pathVar;
}
return null;
}
@Override
public IEnvironmentVariable[] getVariables() {
return envVars;
}
@Override
public Path getCommandPath(Path command) {
if (command.isAbsolute()) {
return command;
}
if (Platform.getOS().equals(Platform.OS_WIN32)) {
if (!command.toString().endsWith(".exe")) { //$NON-NLS-1$
command = Paths.get(command.toString() + ".exe"); //$NON-NLS-1$
}
}
if (path != null) {
for (Path p : path) {
Path c = p.resolve(command);
if (Files.isExecutable(c)) {
return c;
}
}
}
// Look for it in the path environment var
String path = System.getenv("PATH"); //$NON-NLS-1$
for (String entry : path.split(File.pathSeparator)) {
Path entryPath = Paths.get(entry);
Path cmdPath = entryPath.resolve(command);
if (Files.isExecutable(cmdPath)) {
return cmdPath;
}
}
return null;
}
@Override
public String[] getCompileCommands() {
if (compileCommands == null) {
List<String> cmds = new ArrayList<>();
for (String cmd : new String[] { "gcc", "g++", "clang", "clang++" }) { //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
cmd = prefix != null ? cmd : prefix + cmd;
Path cmdPath = getCommandPath(Paths.get(cmd));
if (cmdPath != null) {
cmds.add(cmd);
}
}
compileCommands = cmds.toArray(new String[compileCommands.length]);
}
return compileCommands;
}
@Override
public IResource[] getResourcesFromCommand(String[] cmd, URI buildDirectoryURI) {
// Start at the back looking for arguments
List<IResource> resources = new ArrayList<>();
IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot();
for (int i = cmd.length - 1; i >= 0; --i) {
String arg = cmd[i];
if (arg.startsWith("-")) { //$NON-NLS-1$
// ran into an option, we're done.
break;
}
for (IFile resource : root.findFilesForLocationURI(buildDirectoryURI.resolve(arg))) {
resources.add(resource);
}
}
return resources.toArray(new IResource[resources.size()]);
}
}