mirror of
https://github.com/eclipse-cdt/cdt
synced 2025-06-07 17:56:01 +02:00
Bug 566385: add cmake build-output parser.
Change-Id: I37af477454a7c587bcfdc470e9e54c6b3aa08a14 Signed-off-by: Martin Weber <fifteenknots505@gmail.com>
This commit is contained in:
parent
eee4440071
commit
44ebe33262
6 changed files with 482 additions and 3 deletions
|
@ -12,3 +12,4 @@ pluginName=CDT CMake Core
|
|||
providerName=Eclipse CDT
|
||||
cmakenature.name=CMake Nature
|
||||
cmaketoolChainProvider.name=CMake ToolChain File Provider
|
||||
cmakeproblem=CMake Problem
|
|
@ -20,4 +20,13 @@
|
|||
natureId="org.eclipse.cdt.cmake.core.cmakeNature">
|
||||
</provider>
|
||||
</extension>
|
||||
<extension
|
||||
id="cmakeproblem"
|
||||
name="%cmakeproblem"
|
||||
point="org.eclipse.core.resources.markers">
|
||||
<super type="org.eclipse.core.resources.problemmarker">
|
||||
</super>
|
||||
<super type="org.eclipse.core.resources.textmarker">
|
||||
</super>
|
||||
</extension>
|
||||
</plugin>
|
||||
|
|
|
@ -136,7 +136,8 @@ public class CMakeBuildConfiguration extends CBuildConfiguration {
|
|||
runCMake = !Files.exists(buildDir.resolve("CMakeFiles")); //$NON-NLS-1$
|
||||
}
|
||||
|
||||
if (runCMake) { // $NON-NLS-1$
|
||||
if (runCMake) {
|
||||
CMakeBuildConfiguration.deleteCMakeErrorMarkers(project);
|
||||
|
||||
console.getOutputStream().write(String.format(Messages.CMakeBuildConfiguration_Configuring, buildDir));
|
||||
// clean output to make sure there is no content
|
||||
|
@ -175,6 +176,7 @@ public class CMakeBuildConfiguration extends CBuildConfiguration {
|
|||
|
||||
org.eclipse.core.runtime.Path workingDir = new org.eclipse.core.runtime.Path(
|
||||
getBuildDirectory().toString());
|
||||
// TODO hook in cmake error parsing here
|
||||
Process p = startBuildProcess(command, new IEnvironmentVariable[0], workingDir, console, monitor);
|
||||
if (p == null) {
|
||||
console.getErrorStream().write(String.format(Messages.CMakeBuildConfiguration_Failure, "")); //$NON-NLS-1$
|
||||
|
@ -320,7 +322,7 @@ public class CMakeBuildConfiguration extends CBuildConfiguration {
|
|||
* Recursively removes any files and directories found below the specified Path.
|
||||
*/
|
||||
private static void cleanDirectory(Path dir) throws IOException {
|
||||
SimpleFileVisitor<Path> deltor = new SimpleFileVisitor<Path>() {
|
||||
SimpleFileVisitor<Path> deltor = new SimpleFileVisitor<>() {
|
||||
@Override
|
||||
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
|
||||
Files.delete(file);
|
||||
|
@ -455,4 +457,15 @@ public class CMakeBuildConfiguration extends CBuildConfiguration {
|
|||
@Override
|
||||
public void shutdown() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes all CMake error markers on the specified project.
|
||||
*
|
||||
* @param project
|
||||
* the project where to remove the error markers.
|
||||
* @throws CoreException
|
||||
*/
|
||||
private static void deleteCMakeErrorMarkers(IProject project) throws CoreException {
|
||||
project.deleteMarkers(CMakeErrorParser.CMAKE_PROBLEM_MARKER_ID, false, IResource.DEPTH_INFINITE);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,454 @@
|
|||
/*******************************************************************************
|
||||
* Copyright (c) 2013-2020 Martin Weber.
|
||||
*
|
||||
* Content is provided to you under the terms and conditions of the Eclipse Public License Version 2.0 "EPL".
|
||||
* A copy of the EPL is available at http://www.eclipse.org/legal/epl-2.0.
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*******************************************************************************/
|
||||
package org.eclipse.cdt.cmake.core.internal;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.util.Objects;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.eclipse.core.resources.IContainer;
|
||||
import org.eclipse.core.resources.IMarker;
|
||||
import org.eclipse.core.runtime.CoreException;
|
||||
import org.eclipse.core.runtime.IPath;
|
||||
import org.eclipse.core.runtime.IStatus;
|
||||
import org.eclipse.core.runtime.Path;
|
||||
import org.eclipse.core.runtime.Status;
|
||||
|
||||
/** Parses the output of cmake and reports errors and warnings as problem markers.
|
||||
*
|
||||
* @author Martin Weber
|
||||
*/
|
||||
/* package */ class CMakeErrorParser extends OutputStream {
|
||||
|
||||
public static final String CMAKE_PROBLEM_MARKER_ID = Activator.getId() + ".cmakeproblem"; //$NON-NLS-1$
|
||||
|
||||
/**
|
||||
* Taken from cmMessenger.cxx#printMessagePreamble: <code>
|
||||
*
|
||||
* <pre>
|
||||
if (t == cmake::FATAL_ERROR) {
|
||||
msg << "CMake Error";
|
||||
} else if (t == cmake::INTERNAL_ERROR) {
|
||||
msg << "CMake Internal Error (please report a bug)";
|
||||
} else if (t == cmake::LOG) {
|
||||
msg << "CMake Debug Log";
|
||||
} else if (t == cmake::DEPRECATION_ERROR) {
|
||||
msg << "CMake Deprecation Error";
|
||||
} else if (t == cmake::DEPRECATION_WARNING) {
|
||||
msg << "CMake Deprecation Warning";
|
||||
} else if (t == cmake::AUTHOR_WARNING) {
|
||||
msg << "CMake Warning (dev)";
|
||||
} else if (t == cmake::AUTHOR_ERROR) {
|
||||
msg << "CMake Error (dev)";
|
||||
} else {
|
||||
msg << "CMake Warning";
|
||||
* </pre>
|
||||
*
|
||||
* <code><br>
|
||||
* NOTE: We currently not handle output emitted by the cmake MESSAGE() command since the msg format
|
||||
* is unknown (or needs more investigation).
|
||||
*
|
||||
*/
|
||||
// message start markers...
|
||||
private static final String START_DERROR = "CMake Deprecation Error"; //$NON-NLS-1$
|
||||
private static final String START_DWARNING = "CMake Deprecation Warning"; //$NON-NLS-1$
|
||||
private static final String START_ERROR = "CMake Error"; //$NON-NLS-1$
|
||||
private static final String START_ERROR_DEV = "CMake Error (dev)"; //$NON-NLS-1$
|
||||
private static final String START_IERROR = "CMake Internal Error (please report a bug)"; //$NON-NLS-1$
|
||||
private static final String START_LOG = "CMake Debug Log"; //$NON-NLS-1$
|
||||
private static final String START_WARNING = "CMake Warning"; //$NON-NLS-1$
|
||||
private static final String START_WARNING_DEV = "CMake Warning (dev)"; //$NON-NLS-1$
|
||||
private static final String START_STATUS = "--"; //$NON-NLS-1$
|
||||
/** to terminate on the output of 'message("message test")' */
|
||||
private static final String START_MSG_SIMPLE = "\\R\\R"; //$NON-NLS-1$
|
||||
/** Start of a new error message, also ending the previous message. */
|
||||
private static final Pattern PTN_MSG_START;
|
||||
|
||||
/** Name of the named-capturing group that holds a file name. */
|
||||
private static final String GP_FILE = "File"; //$NON-NLS-1$
|
||||
/** Name of the named-capturing group that holds a line number. */
|
||||
private static final String GP_LINE = "Lineno"; //$NON-NLS-1$
|
||||
|
||||
static {
|
||||
String ptn = "^" + String.join("|", START_DERROR, START_DWARNING, Pattern.quote(START_ERROR_DEV), START_ERROR, //$NON-NLS-1$ //$NON-NLS-2$
|
||||
Pattern.quote(START_IERROR), START_LOG, Pattern.quote(START_WARNING_DEV), START_WARNING, START_STATUS,
|
||||
START_MSG_SIMPLE);
|
||||
PTN_MSG_START = Pattern.compile(ptn);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// the source root of the project being built
|
||||
private final IContainer srcPath;
|
||||
private final OutputStream os;
|
||||
|
||||
private final StringBuilder buffer;
|
||||
|
||||
/** <code>false</code> as long as the buffer contains leading junk in front of a start-of-message */
|
||||
private boolean somSeen;
|
||||
|
||||
/**
|
||||
* @param srcFolder
|
||||
* the source root of the project being built
|
||||
* @param outputStream
|
||||
* the OutputStream to write to or {@code null}
|
||||
*/
|
||||
public CMakeErrorParser(IContainer srcFolder, OutputStream outputStream) {
|
||||
this.srcPath = Objects.requireNonNull(srcFolder);
|
||||
this.os = outputStream;
|
||||
buffer = new StringBuilder(512);
|
||||
}
|
||||
|
||||
private void processBuffer(boolean isEOF) {
|
||||
Matcher matcher = PTN_MSG_START.matcher(""); //$NON-NLS-1$
|
||||
for (;;) {
|
||||
matcher.reset(buffer);
|
||||
if (matcher.find()) {
|
||||
// a new Start Of Message is present in the buffer
|
||||
if (!somSeen) {
|
||||
// this is the first Start Of Message seen in the output, discard leading junk
|
||||
buffer.delete(0, matcher.start());
|
||||
somSeen = true;
|
||||
return;
|
||||
}
|
||||
String classification = matcher.group();
|
||||
int start = matcher.end();
|
||||
// get start of next message
|
||||
if (matcher.find() || isEOF) {
|
||||
int end = isEOF ? buffer.length() : matcher.start();
|
||||
String fullMessage = buffer.substring(0, end);
|
||||
// System.err.println("-###" + fullMessage.trim() + "\n###-");
|
||||
String content = buffer.substring(start, end);
|
||||
// buffer contains a complete message
|
||||
processMessage(classification, content, fullMessage);
|
||||
buffer.delete(0, end);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
// nothing found in buffer
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param classification
|
||||
* message classification string
|
||||
* @param content
|
||||
* message content, which is parsed according to the classification
|
||||
* @param fullMessage
|
||||
* the complete message, including the classification
|
||||
*/
|
||||
private void processMessage(String classification, String content, String fullMessage) {
|
||||
MarkerCreator creator;
|
||||
switch (classification) {
|
||||
case START_DERROR:
|
||||
creator = new McDeprError(srcPath);
|
||||
break;
|
||||
case START_DWARNING:
|
||||
creator = new McDeprWarning(srcPath);
|
||||
break;
|
||||
case START_ERROR:
|
||||
creator = new McError(srcPath);
|
||||
break;
|
||||
case START_ERROR_DEV:
|
||||
creator = new McErrorDev(srcPath);
|
||||
break;
|
||||
case START_IERROR:
|
||||
creator = new McInternalError(srcPath);
|
||||
break;
|
||||
case START_WARNING:
|
||||
creator = new McWarning(srcPath);
|
||||
break;
|
||||
case START_WARNING_DEV:
|
||||
creator = new McWarningDev(srcPath);
|
||||
break;
|
||||
default:
|
||||
return; // ignore message
|
||||
}
|
||||
|
||||
try {
|
||||
creator.createMarker(fullMessage, content);
|
||||
} catch (CoreException e) {
|
||||
Activator.getPlugin().getLog()
|
||||
.log(new Status(IStatus.WARNING, Activator.getId(), "CMake output error parsing failed", e)); //$NON-NLS-1$
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(int c) throws IOException {
|
||||
if (os != null)
|
||||
os.write(c);
|
||||
buffer.append(new String(new byte[] { (byte) c }));
|
||||
processBuffer(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(byte[] b, int off, int len) throws IOException {
|
||||
if (os != null)
|
||||
os.write(b, off, len);
|
||||
buffer.append(new String(b, off, len));
|
||||
processBuffer(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void flush() throws IOException {
|
||||
if (os != null)
|
||||
os.flush();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
if (os != null)
|
||||
os.close();
|
||||
// process remaining bytes
|
||||
processBuffer(true);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// inner classes
|
||||
////////////////////////////////////////////////////////////////////
|
||||
/**
|
||||
* Marker creator base class. Extracts the source-file name and line-number of errors from the output stream.
|
||||
*
|
||||
* @author Martin Weber
|
||||
*/
|
||||
private static abstract class MarkerCreator {
|
||||
|
||||
/** patterns used to extract file-name and line number information */
|
||||
private static final Pattern[] PTN_LOCATION;
|
||||
|
||||
static {
|
||||
PTN_LOCATION = new Pattern[] {
|
||||
Pattern.compile("(?m)^ at (?<" + GP_FILE + ">.+):(?<" + GP_LINE + ">\\d+).*$"), //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
|
||||
Pattern.compile("(?s)^: Error in cmake code at.(?<" + GP_FILE + ">.+):(?<" + GP_LINE + ">\\d+).*$"), //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
|
||||
Pattern.compile("(?m)^ in (?<" + GP_FILE + ">.+):(?<" + GP_LINE + ">\\d+).*$"), //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
|
||||
Pattern.compile("(?m)^:\\s.+$"), }; //$NON-NLS-1$
|
||||
}
|
||||
|
||||
// the source root of the project being built
|
||||
protected final IContainer srcPath;
|
||||
|
||||
/**
|
||||
* @param srcPath
|
||||
* the source root of the project being built
|
||||
*/
|
||||
public MarkerCreator(IContainer srcPath) {
|
||||
this.srcPath = srcPath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the message classification that this object handles.
|
||||
*/
|
||||
abstract String getClassification();
|
||||
|
||||
/**
|
||||
* @return the severity of the problem, see {@link IMarker} for acceptable severity values
|
||||
*/
|
||||
abstract int getSeverity();
|
||||
|
||||
/**
|
||||
* Creates the {@link IMarker marker object} that reflects the message.
|
||||
*
|
||||
* @param fullMessage
|
||||
* the complete message, including the classification
|
||||
* @param content
|
||||
* the message, without the classification
|
||||
* @throws CoreException
|
||||
*/
|
||||
public void createMarker(String fullMessage, String content) throws CoreException {
|
||||
for (Pattern ptn : PTN_LOCATION) {
|
||||
final Matcher matcher = ptn.matcher(content);
|
||||
if (matcher.find()) {
|
||||
// normally project source root relative but may be absolute FS path
|
||||
String filename = null;
|
||||
try {
|
||||
filename = matcher.group(GP_FILE);
|
||||
} catch (IllegalArgumentException expected) {
|
||||
}
|
||||
IMarker marker = createBasicMarker(filename, getSeverity(), fullMessage.trim());
|
||||
try {
|
||||
String lineno = matcher.group(GP_LINE);
|
||||
Integer lineNumber = Integer.parseInt(lineno);
|
||||
marker.setAttribute(IMarker.LINE_NUMBER, lineNumber);
|
||||
} catch (IllegalArgumentException expected) {
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a basic problem marker which should be enhanced with more problem information (e.g. severity, file name,
|
||||
* line number exact message).
|
||||
*
|
||||
* @param fileName
|
||||
* the file where the problem occurred, relative to the source-root or <code>null</code> to denote just the
|
||||
* current project being build
|
||||
* @param severity
|
||||
* the severity of the problem, see {@link IMarker} for acceptable severity values
|
||||
* @param fullMessage
|
||||
* the complete message, including the classification
|
||||
* @throws CoreException
|
||||
*/
|
||||
protected final IMarker createBasicMarker(String fileName, int severity, String fullMessage)
|
||||
throws CoreException {
|
||||
IMarker marker;
|
||||
if (fileName == null) {
|
||||
marker = srcPath.createMarker(CMAKE_PROBLEM_MARKER_ID);
|
||||
} else {
|
||||
// NOTE normally, cmake reports the file name relative to source root.
|
||||
// BUT some messages give an absolute file-system path which is problematic when the build
|
||||
// runs in a docker container
|
||||
// So we do some heuristics here...
|
||||
IPath path = new Path(fileName);
|
||||
try {
|
||||
// normal case: file is rel. to source root
|
||||
marker = srcPath.getFile(path).createMarker(CMAKE_PROBLEM_MARKER_ID);
|
||||
} catch (CoreException ign) {
|
||||
// try abs. path
|
||||
IPath srcLocation = srcPath.getLocation();
|
||||
if (srcLocation.isPrefixOf(path)) {
|
||||
// can resolve the cmake file
|
||||
int segmentsToRemove = srcLocation.segmentCount();
|
||||
path = path.removeFirstSegments(segmentsToRemove);
|
||||
marker = srcPath.getFile(path).createMarker(CMAKE_PROBLEM_MARKER_ID);
|
||||
} else {
|
||||
// possibly a build in docker container. we would reach this if the source-dir path inside
|
||||
// the container is NOT the same as the one in the host/IDE.
|
||||
// for now, just add the markers to the source dir and lets users file issues:-)
|
||||
marker = srcPath.createMarker(CMAKE_PROBLEM_MARKER_ID);
|
||||
Activator.getPlugin().getLog().log(new Status(IStatus.INFO, Activator.getId(),
|
||||
String.format(Messages.CMakeErrorParser_NotAWorkspaceResource, fileName)));
|
||||
// Extra case: IDE runs on Linux, build runs on Windows, or vice versa...
|
||||
}
|
||||
}
|
||||
}
|
||||
marker.setAttribute(IMarker.MESSAGE, fullMessage);
|
||||
marker.setAttribute(IMarker.SEVERITY, severity);
|
||||
marker.setAttribute(IMarker.LOCATION, CMakeErrorParser.class.getName());
|
||||
return marker;
|
||||
}
|
||||
} // MarkerCreator
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
private static class McDeprError extends MarkerCreator {
|
||||
public McDeprError(IContainer srcPath) {
|
||||
super(srcPath);
|
||||
}
|
||||
|
||||
@Override
|
||||
String getClassification() {
|
||||
return START_DERROR;
|
||||
}
|
||||
|
||||
@Override
|
||||
int getSeverity() {
|
||||
return IMarker.SEVERITY_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
private static class McDeprWarning extends MarkerCreator {
|
||||
public McDeprWarning(IContainer srcPath) {
|
||||
super(srcPath);
|
||||
}
|
||||
|
||||
@Override
|
||||
String getClassification() {
|
||||
return START_DWARNING;
|
||||
}
|
||||
|
||||
@Override
|
||||
int getSeverity() {
|
||||
return IMarker.SEVERITY_WARNING;
|
||||
}
|
||||
}
|
||||
|
||||
private static class McError extends MarkerCreator {
|
||||
public McError(IContainer srcPath) {
|
||||
super(srcPath);
|
||||
}
|
||||
|
||||
@Override
|
||||
String getClassification() {
|
||||
return START_ERROR;
|
||||
}
|
||||
|
||||
@Override
|
||||
int getSeverity() {
|
||||
return IMarker.SEVERITY_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
private static class McErrorDev extends MarkerCreator {
|
||||
public McErrorDev(IContainer srcPath) {
|
||||
super(srcPath);
|
||||
}
|
||||
|
||||
@Override
|
||||
String getClassification() {
|
||||
return START_ERROR_DEV;
|
||||
}
|
||||
|
||||
@Override
|
||||
int getSeverity() {
|
||||
return IMarker.SEVERITY_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
private static class McInternalError extends MarkerCreator {
|
||||
public McInternalError(IContainer srcPath) {
|
||||
super(srcPath);
|
||||
}
|
||||
|
||||
@Override
|
||||
String getClassification() {
|
||||
return START_IERROR;
|
||||
}
|
||||
|
||||
@Override
|
||||
int getSeverity() {
|
||||
return IMarker.SEVERITY_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
private static class McWarning extends MarkerCreator {
|
||||
public McWarning(IContainer srcPath) {
|
||||
super(srcPath);
|
||||
}
|
||||
|
||||
@Override
|
||||
String getClassification() {
|
||||
return START_WARNING;
|
||||
}
|
||||
|
||||
@Override
|
||||
int getSeverity() {
|
||||
return IMarker.SEVERITY_WARNING;
|
||||
}
|
||||
}
|
||||
|
||||
private static class McWarningDev extends MarkerCreator {
|
||||
public McWarningDev(IContainer srcPath) {
|
||||
super(srcPath);
|
||||
}
|
||||
|
||||
@Override
|
||||
String getClassification() {
|
||||
return START_WARNING_DEV;
|
||||
}
|
||||
|
||||
@Override
|
||||
int getSeverity() {
|
||||
return IMarker.SEVERITY_WARNING;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -25,6 +25,7 @@ public class Messages extends NLS {
|
|||
public static String CMakeBuildConfiguration_ProcCompCmds;
|
||||
public static String CMakeBuildConfiguration_ProcCompJson;
|
||||
public static String CMakeBuildConfiguration_Failure;
|
||||
public static String CMakeErrorParser_NotAWorkspaceResource;
|
||||
static {
|
||||
// initialize resource bundle
|
||||
NLS.initializeMessages(BUNDLE_NAME, Messages.class);
|
||||
|
|
|
@ -19,3 +19,4 @@ CMakeBuildConfiguration_NoToolchainFile=No CMake toolchain file found for this t
|
|||
CMakeBuildConfiguration_ProcCompCmds=Processing compile commands %s
|
||||
CMakeBuildConfiguration_ProcCompJson=Processing compile_commands.json
|
||||
CMakeBuildConfiguration_Failure=Failure running cmake: %s\n
|
||||
CMakeErrorParser_NotAWorkspaceResource=Could not map %s to a workspace resource. Did the build run in a container?
|
||||
|
|
Loading…
Add table
Reference in a new issue