blob: dec8c2aebd4681cf981d077cd7a448c8be6cfdab [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2004, 2016 IBM Corporation and others.
*
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* IBM - Initial API and implementation
*******************************************************************************/
package org.eclipse.cdt.make.xlc.core.scannerconfig;
import java.io.File;
import java.io.IOException;
import java.net.URI;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Vector;
import org.eclipse.cdt.core.IMarkerGenerator;
import org.eclipse.cdt.core.ProblemMarkerInfo;
import org.eclipse.cdt.make.core.MakeCorePlugin;
import org.eclipse.cdt.make.internal.core.MakeMessages;
import org.eclipse.cdt.make.internal.core.scannerconfig.util.CCommandDSC;
import org.eclipse.cdt.make.internal.core.scannerconfig.util.KVStringPair;
import org.eclipse.cdt.make.internal.core.scannerconfig.util.SCDOptionsEnum;
import org.eclipse.cdt.make.internal.core.scannerconfig.util.TraceUtil;
import org.eclipse.cdt.make.xlc.core.scannerconfig.util.XLCCommandDSC;
import org.eclipse.cdt.utils.EFSExtensionManager;
import org.eclipse.core.resources.IContainer;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IWorkspaceRoot;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.Path;
/**
* @author crecoskie
*
*/
public class XLCBuildOutputParserUtility {
protected class Problem {
protected String description;
protected IResource file;
protected int lineNumber;
protected int severity;
protected String variableName;
public Problem(IResource file, int lineNumber, String desciption, int severity, String variableName) {
this.file = file;
this.lineNumber = lineNumber;
this.description = desciption;
this.severity = severity;
this.variableName = variableName;
}
}
public static IPath convertCygpath(IPath path) {
if (path.segmentCount() > 1 && path.segment(0).equals("cygdrive")) { //$NON-NLS-1$
StringBuilder buf = new StringBuilder(2);
buf.append(Character.toUpperCase(path.segment(1).charAt(0)));
buf.append(':');
path = path.removeFirstSegments(2);
path = path.setDevice(buf.toString());
path = path.makeAbsolute();
}
return path;
}
private List commandsList2;
private int commandsN = 0;
private List compiledFileList;
private Map directoryCommandListMap;
private IPath fBaseDirectory;
private String fDefaultMacroDefinitionValue = "1"; //$NON-NLS-1$
private Vector<IPath> fDirectoryStack;
private ArrayList<Problem> fErrors;
private int filesN = 0;
private IMarkerGenerator fMarkerGenerator;
private IProject project;
private int workingDirsN = 0;
/*
* For tracking the location of files being compiled
*/
private Map<String, IFile> fFilesInProject;
private List<String> fCollectedFiles;
private List<String> fNameConflicts;
protected XLCBuildOutputParserUtility(IPath baseDirectory, IPath workingDirectory) {
fDirectoryStack = new Vector<>();
fErrors = new ArrayList<>();
this.fBaseDirectory = baseDirectory;
if (workingDirectory != null) {
pushDirectory(workingDirectory);
}
}
/**
*
*/
public XLCBuildOutputParserUtility(IProject project, IPath workingDirectory, IMarkerGenerator markerGenerator) {
fDirectoryStack = new Vector<>();
fErrors = new ArrayList<>();
this.project = project;
fBaseDirectory = getPathForResource(project);
if (workingDirectory != null) {
pushDirectory(workingDirectory);
}
}
private IPath getPathForResource(IResource resource) {
// TODO: when the file system utility stuff is in, this will have to call it to get the path
// for now, get the path from the URI
URI locationURI = resource.getLocationURI();
IPath path = new Path(locationURI.getPath());
return path;
}
/**
* Adds a mapping filename, generic_command
* @param longFileName
* @param genericLine
*/
void addGenericCommandForFile(String longFileName, String genericCommand) {
// if a file name has already been added once, return
if (compiledFileList.contains(longFileName))
return;
compiledFileList.add(longFileName);
String workingDir = getWorkingDirectory().toString();
List directoryCommandList = (List) directoryCommandListMap.get(workingDir);
if (directoryCommandList == null) {
directoryCommandList = new ArrayList();
directoryCommandListMap.put(workingDir, directoryCommandList);
++workingDirsN;
}
Map command21FileListMap = null;
for (Iterator i = directoryCommandList.iterator(); i.hasNext();) {
command21FileListMap = (Map) i.next();
List fileList = (List) command21FileListMap.get(genericCommand);
if (fileList != null) {
if (!fileList.contains(longFileName)) {
fileList.add(longFileName);
++filesN;
}
return;
}
}
command21FileListMap = new HashMap(1);
directoryCommandList.add(command21FileListMap);
++commandsN;
List fileList = new ArrayList();
command21FileListMap.put(genericCommand, fileList);
fileList.add(longFileName);
++filesN;
}
/**
* Adds a mapping command line -> file, this time without a dir
* @param longFileName
* @param genericLine
*/
void addGenericCommandForFile2(String longFileName, String genericLine) {
// if a file name has already been added once, return
if (compiledFileList.contains(longFileName))
return;
compiledFileList.add(longFileName);
String[] tokens = genericLine.split("\\s+"); //$NON-NLS-1$
CCommandDSC command = getNewCCommandDSC(tokens, 0, false); // assume .c file type
int index = commandsList2.indexOf(command);
if (index == -1) {
commandsList2.add(command);
++commandsN;
} else {
command = (CCommandDSC) commandsList2.get(index);
}
// // add a file
// command.addFile(longFileName);
// ++filesN;
}
public void changeMakeDirectory(String dir, int dirLevel, boolean enterDir) {
if (enterDir) {
/* Sometimes make screws up the output, so
* "leave" events can't be seen. Double-check level
* here.
*/
for (int parseLevel = getDirectoryLevel(); dirLevel < parseLevel; parseLevel = getDirectoryLevel()) {
popDirectory();
}
pushDirectory(new Path(dir));
} else {
popDirectory();
/* Could check to see if they match */
}
}
/**
* Called by the console line parsers to generate a problem marker.
*/
public void generateMarker(IResource file, int lineNumber, String desc, int severity, String varName) {
// No need to collect markers if marker generator is not present
if (fMarkerGenerator != null) {
Problem problem = new Problem(file, lineNumber, desc, severity, varName);
fErrors.add(problem);
}
}
/**
*
*/
void generateReport() {
TraceUtil.metricsTrace("Stats for directory ", //$NON-NLS-1$
"Generic command: '", "' applicable for:", //$NON-NLS-1$ //$NON-NLS-2$
directoryCommandListMap);
TraceUtil.summaryTrace("Discovery summary", workingDirsN, commandsN, filesN); //$NON-NLS-1$
}
/**
* @param filePath : String
* @return filePath : IPath - not <code>null</code>
*/
public IPath getAbsolutePath(String filePath) {
IPath pFilePath;
if (filePath.startsWith("/")) { //$NON-NLS-1$
return convertCygpath(new Path(filePath));
} else if (filePath.startsWith("\\") || //$NON-NLS-1$
(!filePath.startsWith(".") && //$NON-NLS-1$
filePath.length() > 2 && filePath.charAt(1) == ':'
&& (filePath.charAt(2) == '\\' || filePath.charAt(2) == '/'))) {
// absolute path
pFilePath = new Path(filePath);
} else {
// relative path
IPath cwd = getWorkingDirectory();
if (!cwd.isAbsolute()) {
cwd = getBaseDirectory().append(cwd);
}
if (filePath.startsWith("`pwd`")) { //$NON-NLS-1$
if (filePath.length() > 5 && (filePath.charAt(5) == '/' || filePath.charAt(5) == '\\')) {
filePath = filePath.substring(6);
} else {
filePath = filePath.substring(5);
}
}
pFilePath = cwd.append(filePath);
}
return pFilePath;
}
/**
* @return Returns the fBaseDirectory.
*/
public IPath getBaseDirectory() {
return fBaseDirectory;
}
/**
* Returns all CCommandDSC collected so far.
* Currently this list is not filled, so it will always return an empty list.
* @return List of CCommandDSC
*/
public List getCCommandDSCList() {
return new ArrayList(commandsList2);
}
protected int getDirectoryLevel() {
return fDirectoryStack.size();
}
/**
* @return Returns the fDirectoryStack.
*/
protected Vector<IPath> getDirectoryStack() {
return fDirectoryStack;
}
/**
* @return Returns the fErrors.
*/
protected ArrayList<Problem> getErrors() {
return fErrors;
}
/**
* @return Returns the fMarkerGenerator.
*/
protected IMarkerGenerator getMarkerGenerator() {
return fMarkerGenerator;
}
/**
* @param genericLine
* @param cppFileType
* @return CCommandDSC compile command description
*/
public CCommandDSC getNewCCommandDSC(String[] tokens, final int idxOfCompilerCommand, boolean cppFileType) {
ArrayList dirafter = new ArrayList();
ArrayList includes = new ArrayList();
XLCCommandDSC command = new XLCCommandDSC(cppFileType, getProject());
command.addSCOption(new KVStringPair(SCDOptionsEnum.COMMAND.toString(), tokens[idxOfCompilerCommand]));
for (int i = idxOfCompilerCommand + 1; i < tokens.length; ++i) {
String token = tokens[i];
//Target specific options: see GccScannerInfoConsoleParser
if (token.startsWith("-m") || //$NON-NLS-1$
token.equals("-ansi") || //$NON-NLS-1$
token.equals("-posix") || //$NON-NLS-1$
token.equals("-pthread") || //$NON-NLS-1$
token.startsWith("-O") || //$NON-NLS-1$
token.equals("-fno-inline") || //$NON-NLS-1$
token.startsWith("-finline") || //$NON-NLS-1$
token.equals("-fno-exceptions") || //$NON-NLS-1$
token.equals("-fexceptions") || //$NON-NLS-1$
token.equals("-fshort-wchar") || //$NON-NLS-1$
token.equals("-fshort-double") || //$NON-NLS-1$
token.equals("-fno-signed-char") || //$NON-NLS-1$
token.equals("-fsigned-char") || //$NON-NLS-1$
token.startsWith("-fabi-version=") //$NON-NLS-1$
) {
command.addSCOption(new KVStringPair(SCDOptionsEnum.COMMAND.toString(), token));
continue;
}
for (int j = SCDOptionsEnum.MIN; j <= SCDOptionsEnum.MAX; ++j) {
final SCDOptionsEnum optionKind = SCDOptionsEnum.getSCDOptionsEnum(j);
if (token.startsWith(optionKind.toString())) {
String option = token.substring(optionKind.toString().length()).trim();
if (option.length() > 0) {
// ex. -I/dir
} else if (optionKind.equals(SCDOptionsEnum.IDASH)) {
for (Iterator iter = includes.iterator(); iter.hasNext();) {
option = (String) iter.next();
KVStringPair pair = new KVStringPair(SCDOptionsEnum.IQUOTE.toString(), option);
command.addSCOption(pair);
}
includes = new ArrayList();
// -I- has no parameter
} else {
// ex. -I /dir
// take a next token
if (i + 1 < tokens.length && !tokens[i + 1].startsWith("-")) { //$NON-NLS-1$
option = tokens[++i];
} else
break;
}
if (option.length() > 0 && (optionKind.equals(SCDOptionsEnum.INCLUDE)
|| optionKind.equals(SCDOptionsEnum.INCLUDE_FILE)
|| optionKind.equals(SCDOptionsEnum.IMACROS_FILE)
|| optionKind.equals(SCDOptionsEnum.IDIRAFTER) || optionKind.equals(SCDOptionsEnum.ISYSTEM)
|| optionKind.equals(SCDOptionsEnum.IQUOTE))) {
option = (getAbsolutePath(option)).toString();
}
if (optionKind.equals(SCDOptionsEnum.IDIRAFTER)) {
KVStringPair pair = new KVStringPair(SCDOptionsEnum.INCLUDE.toString(), option);
dirafter.add(pair);
} else if (optionKind.equals(SCDOptionsEnum.INCLUDE)) {
includes.add(option);
} else { // add the pair
if (optionKind.equals(SCDOptionsEnum.DEFINE)) {
if (option.indexOf('=') == -1) {
option += '=' + fDefaultMacroDefinitionValue;
}
}
KVStringPair pair = new KVStringPair(optionKind.toString(), option);
command.addSCOption(pair);
}
break;
}
}
}
String option;
for (Iterator iter = includes.iterator(); iter.hasNext();) {
option = (String) iter.next();
KVStringPair pair = new KVStringPair(SCDOptionsEnum.INCLUDE.toString(), option);
command.addSCOption(pair);
}
for (Iterator iter = dirafter.iterator(); iter.hasNext();) {
command.addSCOption((KVStringPair) iter.next());
}
return command;
}
/**
* @return Returns the project.
*/
protected IProject getProject() {
return project;
}
public IPath getWorkingDirectory() {
if (fDirectoryStack.size() != 0) {
return fDirectoryStack.lastElement();
}
// Fallback to the Project Location
// FIXME: if the build did not start in the Project ?
return fBaseDirectory;
}
protected IPath popDirectory() {
int i = getDirectoryLevel();
if (i != 0) {
IPath dir = fDirectoryStack.lastElement();
fDirectoryStack.removeElementAt(i - 1);
return dir;
}
return new Path(""); //$NON-NLS-1$
}
protected void pushDirectory(IPath dir) {
if (dir != null) {
IPath pwd = null;
if (fBaseDirectory != null && fBaseDirectory.isPrefixOf(dir)) {
pwd = dir.removeFirstSegments(fBaseDirectory.segmentCount());
} else {
// check if it is a cygpath
pwd = convertCygpath(dir);
}
fDirectoryStack.addElement(pwd);
}
}
public boolean reportProblems() {
boolean reset = false;
for (Iterator<Problem> iter = fErrors.iterator(); iter.hasNext();) {
Problem problem = iter.next();
if (problem.severity == IMarkerGenerator.SEVERITY_ERROR_BUILD) {
reset = true;
}
if (problem.file == null) {
fMarkerGenerator.addMarker(new ProblemMarkerInfo(project, problem.lineNumber, problem.description,
problem.severity, problem.variableName));
} else {
fMarkerGenerator.addMarker(new ProblemMarkerInfo(problem.file, problem.lineNumber, problem.description,
problem.severity, problem.variableName));
}
}
fErrors.clear();
return reset;
}
public void setDefaultMacroDefinitionValue(String val) {
if (val != null) {
fDefaultMacroDefinitionValue = val;
}
}
public String getDefaultMacroDefinitionValue() {
return fDefaultMacroDefinitionValue;
}
public String normalizePath(String path) {
int column = path.indexOf(':');
if (column > 0) {
char driveLetter = path.charAt(column - 1);
if (Character.isLowerCase(driveLetter)) {
StringBuilder sb = new StringBuilder();
if (column - 1 > 0) {
sb.append(path.substring(0, column - 1));
}
sb.append(Character.toUpperCase(driveLetter));
sb.append(path.substring(column));
path = sb.toString();
}
}
if (path.indexOf('.') == -1 || path.equals(".")) { //$NON-NLS-1$
return (new Path(path)).toString(); // convert separators to '/'
}
// lose "./" segments since they confuse the Path normalization
StringBuilder buf = new StringBuilder(path);
int len = buf.length();
StringBuilder newBuf = new StringBuilder(buf.length());
int scp = 0; // starting copy point
int ssp = 0; // starting search point
int sdot;
boolean validPrefix;
while (ssp < len && (sdot = buf.indexOf(".", ssp)) != -1) { //$NON-NLS-1$
validPrefix = false;
int ddot = buf.indexOf("..", ssp);//$NON-NLS-1$
if (sdot < ddot || ddot == -1) {
newBuf.append(buf.substring(scp, sdot));
scp = sdot;
ssp = sdot + 1;
if (ssp < len) {
if (sdot == 0 || buf.charAt(sdot - 1) == '/' || buf.charAt(sdot - 1) == '\\') {
validPrefix = true;
}
char nextChar = buf.charAt(ssp);
if (validPrefix && nextChar == '/') {
++ssp;
scp = ssp;
} else if (validPrefix && nextChar == '\\') {
++ssp;
if (ssp < len - 1 && buf.charAt(ssp) == '\\') {
++ssp;
}
scp = ssp;
} else {
// no path delimiter, must be '.' inside the path
scp = ssp - 1;
}
}
} else if (sdot == ddot) {
ssp = sdot + 2;
}
}
newBuf.append(buf.substring(scp, len));
IPath orgPath = new Path(newBuf.toString());
return orgPath.toString();
}
/**
* Called by the console line parsers to find a file with a given name.
* @param fileName
* @return IFile or null
*/
public IFile findFile(String fileName) {
IFile file = findFilePath(fileName);
if (file == null) {
// Try the project's map.
file = findFileName(fileName);
if (file != null) {
// If there is a conflict then try all files in the project.
if (isConflictingName(fileName)) {
file = null;
// Create a problem marker
final String error = MakeMessages.getString("ConsoleParser.Ambiguous_Filepath_Error_Message"); //$NON-NLS-1$
TraceUtil.outputError(error, fileName);
generateMarker(getProject(), -1, error + fileName, IMarkerGenerator.SEVERITY_WARNING, null);
}
}
}
return file;
}
/**
* @param filePath
* @return
*/
protected IFile findFilePath(String filePath) {
IPath path = null;
IPath fp = new Path(filePath);
if (fp.isAbsolute()) {
if (getBaseDirectory().isPrefixOf(fp)) {
int segments = getBaseDirectory().matchingFirstSegments(fp);
path = fp.removeFirstSegments(segments);
} else {
path = fp;
}
} else {
path = getWorkingDirectory().append(filePath);
}
IFile file = null;
// The workspace may throw an IllegalArgumentException
// Catch it and the parser should fallback to scan the entire project.
try {
file = findFileInWorkspace(path);
} catch (Exception e) {
}
// We have to do another try, on Windows for cases like "TEST.C" vs "test.c"
// We use the java.io.File canonical path.
if (file == null || !file.exists()) {
File f = path.toFile();
try {
String canon = f.getCanonicalPath();
path = new Path(canon);
file = findFileInWorkspace(path);
} catch (IOException e1) {
}
}
return (file != null && file.exists()) ? file : null;
}
/**
* @param fileName
* @return
*/
protected IFile findFileName(String fileName) {
IPath path = new Path(fileName);
return fFilesInProject.get(path.lastSegment());
}
protected IFile findFileInWorkspace(IPath path) {
IFile file = null;
if (path.isAbsolute()) {
IWorkspaceRoot root = getProject().getWorkspace().getRoot();
// construct a URI, based on the project's locationURI, that points
// to the given path
URI projectURI = project.getLocationURI();
URI newURI = EFSExtensionManager.getDefault().createNewURIFromPath(projectURI, path.toString());
IFile[] files = root.findFilesForLocationURI(newURI);
for (int i = 0; i < files.length; i++) {
if (files[i].getProject().equals(getProject())) {
file = files[i];
break;
}
}
} else {
file = getProject().getFile(path);
}
return file;
}
protected void collectFiles(IContainer parent, List result) {
try {
IResource[] resources = parent.members();
for (int i = 0; i < resources.length; i++) {
IResource resource = resources[i];
if (resource instanceof IFile) {
result.add(resource);
} else if (resource instanceof IContainer) {
collectFiles((IContainer) resource, result);
}
}
} catch (CoreException e) {
MakeCorePlugin.log(e.getStatus());
}
}
protected boolean isConflictingName(String fileName) {
IPath path = new Path(fileName);
return fNameConflicts.contains(path.lastSegment());
}
public List translateRelativePaths(IFile file, String fileName, List includes) {
List translatedIncludes = new ArrayList(includes.size());
for (Iterator i = includes.iterator(); i.hasNext();) {
String include = (String) i.next();
IPath includePath = new Path(include);
if (!includePath.isAbsolute() && !includePath.isUNC()) { // do not translate UNC paths
// First try the current working directory
IPath cwd = getWorkingDirectory();
if (!cwd.isAbsolute()) {
cwd = getBaseDirectory().append(cwd);
}
IPath filePath = new Path(fileName);
if (!filePath.isAbsolute()) {
// check if the cwd is the right one
// appending fileName to cwd should yield file path
filePath = cwd.append(fileName);
}
if (!filePath.toString().equalsIgnoreCase(file.getLocation().toString())) {
// must be the cwd is wrong
// check if file name starts with ".."
if (fileName.startsWith("..")) { //$NON-NLS-1$
// probably multiple choices for cwd, hopeless
final String error = MakeMessages.getString("ConsoleParser.Working_Directory_Error_Message"); //$NON-NLS-1$
TraceUtil.outputError(error, fileName);
generateMarker(file, -1, error, IMarkerGenerator.SEVERITY_WARNING, fileName);
break;
} else {
// remove common segments at the end
IPath tPath = new Path(fileName);
if (fileName.startsWith(".")) { //$NON-NLS-1$
tPath = tPath.removeFirstSegments(1);
}
// get the file path from the file
filePath = file.getLocation();
IPath lastFileSegment = filePath
.removeFirstSegments(filePath.segmentCount() - tPath.segmentCount());
if (lastFileSegment.matchingFirstSegments(tPath) == tPath.segmentCount()) {
cwd = filePath.removeLastSegments(tPath.segmentCount());
}
}
}
IPath candidatePath = cwd.append(includePath);
File dir = candidatePath.toFile();
include = candidatePath.toString();
if (!dir.exists()) {
final String error = MakeMessages.getString("ConsoleParser.Nonexistent_Include_Path_Error_Message"); //$NON-NLS-1$
TraceUtil.outputError(error, include);
// generateMarker(file, -1, error+include, IMarkerGenerator.SEVERITY_WARNING, fileName);
}
}
// TODO VMIR for now add unresolved paths as well
translatedIncludes.add(include);
}
return translatedIncludes;
}
}