blob: 3236ea2af0e5eb5b28df938a9a62333df5329806 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2015, 2016 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.internal.qt.core.build;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.eclipse.cdt.core.ConsoleOutputStream;
import org.eclipse.cdt.core.ErrorParserManager;
import org.eclipse.cdt.core.IConsoleParser;
import org.eclipse.cdt.core.build.CBuildConfiguration;
import org.eclipse.cdt.core.build.ICBuildConfiguration;
import org.eclipse.cdt.core.build.IToolChain;
import org.eclipse.cdt.core.envvar.IEnvironmentVariable;
import org.eclipse.cdt.core.model.ICModelMarker;
import org.eclipse.cdt.core.parser.ExtendedScannerInfo;
import org.eclipse.cdt.core.parser.IExtendedScannerInfo;
import org.eclipse.cdt.core.parser.IScannerInfo;
import org.eclipse.cdt.core.resources.IConsole;
import org.eclipse.cdt.internal.qt.core.Activator;
import org.eclipse.cdt.qt.core.IQtBuildConfiguration;
import org.eclipse.cdt.qt.core.IQtInstall;
import org.eclipse.cdt.qt.core.IQtInstallListener;
import org.eclipse.cdt.qt.core.IQtInstallManager;
import org.eclipse.cdt.qt.core.QtInstallEvent;
import org.eclipse.core.resources.IBuildConfiguration;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.Status;
import org.osgi.service.prefs.BackingStoreException;
import org.osgi.service.prefs.Preferences;
public class QtBuildConfiguration extends CBuildConfiguration
implements ICBuildConfiguration, IQtBuildConfiguration, IQtInstallListener {
private static final String QTINSTALL_NAME = "cdt.qt.install.name"; //$NON-NLS-1$
private static final String QTINSTALL_SPEC = "cdt.qt.install.spec"; //$NON-NLS-1$
private static final String LAUNCH_MODE = "cdt.qt.launchMode"; //$NON-NLS-1$
private final String qtInstallSpec;
private IQtInstall qtInstall;
private Map<String, String> properties;
public QtBuildConfiguration(IBuildConfiguration config, String name) throws CoreException {
super(config, name);
IQtInstallManager manager = Activator.getService(IQtInstallManager.class);
manager.addListener(this);
Preferences settings = getSettings();
String installName = settings.get(QTINSTALL_NAME, ""); //$NON-NLS-1$
qtInstallSpec = settings.get(QTINSTALL_SPEC, ""); //$NON-NLS-1$
if (!installName.isEmpty()) {
qtInstall = manager.getInstall(Paths.get(installName));
if (qtInstallSpec.isEmpty()) {
// save the spec if it wasn't set
settings.put(QTINSTALL_SPEC, qtInstall.getSpec());
try {
settings.flush();
} catch (BackingStoreException e) {
Activator.log(e);
}
}
}
if (getQtInstall() == null) {
throw new CoreException(
Activator.error(String.format("Qt Install for build configuration %s not found.", name)));
}
String oldLaunchMode = settings.get(LAUNCH_MODE, null);
if (oldLaunchMode != null) {
setLaunchMode(oldLaunchMode);
settings.remove(LAUNCH_MODE);
try {
settings.flush();
} catch (BackingStoreException e) {
Activator.log(e);
}
}
}
QtBuildConfiguration(IBuildConfiguration config, String name, IToolChain toolChain, IQtInstall qtInstall,
String launchMode) throws CoreException {
super(config, name, toolChain, launchMode);
this.qtInstall = qtInstall;
this.qtInstallSpec = qtInstall.getSpec();
IQtInstallManager manager = Activator.getService(IQtInstallManager.class);
manager.addListener(this);
Preferences settings = getSettings();
settings.put(QTINSTALL_NAME, qtInstall.getQmakePath().toString());
settings.put(QTINSTALL_SPEC, qtInstallSpec);
if (launchMode != null) {
settings.put(LAUNCH_MODE, launchMode);
}
try {
settings.flush();
} catch (BackingStoreException e) {
Activator.log(e);
}
}
public IQtInstall getQtInstall() {
if (qtInstall == null && !qtInstallSpec.isEmpty()) {
// find one that matches the spec
IQtInstallManager manager = Activator.getService(IQtInstallManager.class);
Collection<IQtInstall> candidates = manager.getInstall(qtInstallSpec);
if (!candidates.isEmpty()) {
qtInstall = candidates.iterator().next();
}
}
return qtInstall;
}
@Override
public void installChanged(QtInstallEvent event) {
if (event.getType() == QtInstallEvent.REMOVED && event.getInstall().equals(qtInstall)) {
// clear the cache so we refetch later
qtInstall = null;
}
}
@Override
public Path getQmakeCommand() {
return getQtInstall().getQmakePath();
}
@Override
public String[] getQmakeConfig() {
String launchMode = getLaunchMode();
if (launchMode != null) {
switch (launchMode) {
case "run": //$NON-NLS-1$
return new String[] { "CONFIG-=debug_and_release", "CONFIG+=release" }; //$NON-NLS-1$ //$NON-NLS-2$
case "debug": //$NON-NLS-1$
return new String[] { "CONFIG-=debug_and_release", "CONFIG+=debug" }; //$NON-NLS-1$ //$NON-NLS-2$
default:
return new String[] { "CONFIG-=debug_and_release", "CONFIG+=launch_mode_" + launchMode }; //$NON-NLS-1$ //$NON-NLS-2$
}
}
return new String[] { "CONFIG+=debug_and_release", "CONFIG+=launch_modeall" }; //$NON-NLS-1$ //$NON-NLS-2$
}
public Path getProjectFile() {
File projectDir = getProject().getLocation().toFile();
File[] proFiles = projectDir.listFiles((dir, name) -> name.endsWith(".pro")); //$NON-NLS-1$
if (proFiles.length > 0) {
// TODO what if there are more than one.
return proFiles[0].toPath();
} else {
return null;
}
}
@Deprecated
@Override
public Path getProgramPath() throws CoreException {
// TODO get the app name from the .pro file.
String projectName = getProject().getName();
switch (Platform.getOS()) {
case Platform.OS_MACOSX:
Path appFolder = getBuildDirectory().resolve(projectName + ".app"); //$NON-NLS-1$
Path contentsFolder = appFolder.resolve("Contents"); //$NON-NLS-1$
Path macosFolder = contentsFolder.resolve("MacOS"); //$NON-NLS-1$
return macosFolder.resolve(projectName);
case Platform.OS_WIN32:
return getBuildDirectory().resolve(projectName + ".exe"); //$NON-NLS-1$
case Platform.OS_LINUX:
return getBuildDirectory().resolve(projectName);
default:
Path releaseFolder = getBuildDirectory().resolve("release"); //$NON-NLS-1$
return releaseFolder.resolve(projectName);
}
}
public String getProperty(String key) {
if (properties == null) {
List<String> cmd = new ArrayList<>();
cmd.add(getQmakeCommand().toString());
cmd.add("-E"); //$NON-NLS-1$
String[] config = getQmakeConfig();
if (config != null) {
for (String str : config) {
cmd.add(str);
}
}
cmd.add(getProjectFile().toString());
try {
ProcessBuilder processBuilder = new ProcessBuilder(cmd)
.directory(getProjectFile().getParent().toFile());
setBuildEnvironment(processBuilder.environment());
Process proc = processBuilder.start();
try (BufferedReader reader = new BufferedReader(new InputStreamReader(proc.getInputStream()))) {
properties = new HashMap<>();
for (String line = reader.readLine(); line != null; line = reader.readLine()) {
int i = line.indexOf('=');
if (i >= 0) {
String k = line.substring(0, i);
String v = line.substring(i + 1);
properties.put(k.trim(), v.trim());
}
}
}
} catch (IOException e) {
Activator.log(e);
}
}
return properties != null ? properties.get(key) : null;
}
@Override
public IEnvironmentVariable getVariable(String name) {
// TODO Auto-generated method stub
return null;
}
@Override
public IEnvironmentVariable[] getVariables() {
// TODO
return new IEnvironmentVariable[0];
}
@Override
public IScannerInfo getScannerInformation(IResource resource) {
IQtInstall qtInstall = getQtInstall();
String cxx = getProperty("QMAKE_CXX"); //$NON-NLS-1$
if (cxx == null) {
Activator.log("No QMAKE_CXX for " + qtInstall.getSpec()); //$NON-NLS-1$
return null;
}
String[] cxxSplit = cxx.split(" "); //$NON-NLS-1$
Path command = Paths.get(cxxSplit[0]);
List<String> args = new ArrayList<>();
for (int i = 1; i < cxxSplit.length; ++i) {
args.add(cxxSplit[i]);
}
args.addAll(Arrays.asList(getProperty("QMAKE_CXXFLAGS").split(" "))); //$NON-NLS-1$ //$NON-NLS-2$
args.add("-o"); //$NON-NLS-1$
args.add("-"); //$NON-NLS-1$
String srcFile;
if (resource instanceof IFile) {
srcFile = resource.getLocation().toOSString();
// Only add file if it's an IFile
args.add(srcFile);
} else {
// Doesn't matter, the toolchain will create a tmp file for this
srcFile = "scannerInfo.cpp"; //$NON-NLS-1$
}
String[] includePaths = getProperty("INCLUDEPATH").split(" "); //$NON-NLS-1$ //$NON-NLS-2$
for (int i = 0; i < includePaths.length; ++i) {
Path path = Paths.get(includePaths[i]);
if (!path.isAbsolute()) {
Path projectDir = getProjectFile().getParent();
includePaths[i] = projectDir.resolve(path).toString();
}
}
IExtendedScannerInfo baseScannerInfo = new ExtendedScannerInfo(null, includePaths);
try {
return getToolChain().getScannerInfo(getBuildConfiguration(), command,
args.toArray(new String[args.size()]), baseScannerInfo, resource,
getBuildContainer().getLocationURI());
} catch (CoreException e) {
Activator.log(e);
return null;
}
}
@Override
public IProject[] build(int kind, Map<String, String> args, IConsole console, IProgressMonitor monitor)
throws CoreException {
IProject project = getProject();
try {
project.deleteMarkers(ICModelMarker.C_MODEL_PROBLEM_MARKER, false, IResource.DEPTH_INFINITE);
ConsoleOutputStream errStream = console.getErrorStream();
ConsoleOutputStream outStream = console.getOutputStream();
Path makeCommand = getMakeCommand();
if (makeCommand == null) {
errStream.write("'make' not found.\n");
return null;
}
Path buildDir = getBuildDirectory();
if (!buildDir.resolve("Makefile").toFile().exists()) { //$NON-NLS-1$
// Need to run qmake
List<String> command = new ArrayList<>();
command.add(getQmakeCommand().toString());
String[] config = getQmakeConfig();
if (config != null) {
for (String str : config) {
command.add(str);
}
}
IFile projectFile = project.getFile(project.getName() + ".pro"); //$NON-NLS-1$
command.add(projectFile.getLocation().toOSString());
ProcessBuilder processBuilder = new ProcessBuilder(command).directory(getBuildDirectory().toFile());
setBuildEnvironment(processBuilder.environment());
Process process = processBuilder.start();
StringBuffer msg = new StringBuffer();
for (String arg : command) {
msg.append(arg).append(' ');
}
msg.append('\n');
outStream.write(msg.toString());
// TODO qmake error parser
watchProcess(process, new IConsoleParser[0], console);
}
try (ErrorParserManager epm = new ErrorParserManager(project, getBuildDirectoryURI(), this,
getToolChain().getErrorParserIds())) {
// run make
ProcessBuilder processBuilder = new ProcessBuilder(makeCommand.toString(), "all") //$NON-NLS-1$
.directory(buildDir.toFile());
setBuildEnvironment(processBuilder.environment());
Process process = processBuilder.start();
outStream.write(makeCommand.toString() + '\n');
watchProcess(process, new IConsoleParser[] { epm }, console);
}
getProject().refreshLocal(IResource.DEPTH_INFINITE, monitor);
return new IProject[] { project };
} catch (IOException e) {
throw new CoreException(new Status(IStatus.ERROR, Activator.ID, "Building " + project.getName(), e)); //$NON-NLS-1$
}
}
@Override
public void clean(IConsole console, IProgressMonitor monitor) throws CoreException {
IProject project = getProject();
try {
project.deleteMarkers(ICModelMarker.C_MODEL_PROBLEM_MARKER, false, IResource.DEPTH_INFINITE);
ConsoleOutputStream errStream = console.getErrorStream();
ConsoleOutputStream outStream = console.getOutputStream();
Path makeCommand = getMakeCommand();
if (makeCommand == null) {
errStream.write("'make' not found.\n");
return;
}
Path buildDir = getBuildDirectory();
try (ErrorParserManager epm = new ErrorParserManager(project, getBuildDirectoryURI(), this,
getToolChain().getErrorParserIds())) {
// run make
ProcessBuilder processBuilder = new ProcessBuilder(makeCommand.toString(), "clean") //$NON-NLS-1$
.directory(buildDir.toFile());
setBuildEnvironment(processBuilder.environment());
Process process = processBuilder.start();
outStream.write(makeCommand.toString() + "clean\n"); //$NON-NLS-1$
watchProcess(process, new IConsoleParser[] { epm }, console);
}
project.refreshLocal(IResource.DEPTH_INFINITE, monitor);
} catch (IOException e) {
throw new CoreException(new Status(IStatus.ERROR, Activator.ID, "Cleaning " + project.getName(), e)); //$NON-NLS-1$
}
}
public Path getMakeCommand() {
Path makeCommand = findCommand("make"); //$NON-NLS-1$
if (makeCommand == null) {
makeCommand = findCommand("mingw32-make"); //$NON-NLS-1$
}
return makeCommand;
}
}