blob: 6b63ed8b70ecc6d60339dcafd0b2043d11e1fc6d [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2019 Marc-Andre Laperle.
*
* 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
*******************************************************************************/
package org.eclipse.cdt.msw.build.core;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.eclipse.cdt.core.language.settings.providers.ILanguageSettingsEditableProvider;
import org.eclipse.cdt.core.settings.model.ICLanguageSettingEntry;
import org.eclipse.cdt.core.settings.model.ICSettingEntry;
import org.eclipse.cdt.managedbuilder.language.settings.providers.AbstractBuildCommandParser;
/**
* Build command parser capable to parse cl.exe command in build output and generate
* language settings per file being compiled.
*/
public class MSVCBuildCommandParser extends AbstractBuildCommandParser implements ILanguageSettingsEditableProvider {
private static final String DOUBLE_BACKSLASH = "\\\\"; //$NON-NLS-1$
private static final Pattern DOUBLE_BACKSLASH_PATTERN = Pattern.compile(Pattern.quote(DOUBLE_BACKSLASH));
private static final String BACKSLASH_REPLACEMENT_STRING = "\\\\"; //$NON-NLS-1$
private static final String BACKSLASH_QUOTE = "\\\""; //$NON-NLS-1$
private static final Pattern BACKSLASH_QUOTE_PATTERN = Pattern.compile(Pattern.quote(BACKSLASH_QUOTE));
private static final String QUOTE_REPLACEMENT_STRING = "\""; //$NON-NLS-1$
private static String unescapeString(String value) {
// There are probably many other things to unescape but these are the most
// common.
if (value.contains(DOUBLE_BACKSLASH))
value = DOUBLE_BACKSLASH_PATTERN.matcher(value).replaceAll(BACKSLASH_REPLACEMENT_STRING);
if (value.contains(BACKSLASH_QUOTE))
value = BACKSLASH_QUOTE_PATTERN.matcher(value).replaceAll(QUOTE_REPLACEMENT_STRING);
return value;
}
private static class MSVCMacroOptionParser extends MacroOptionParser {
public MSVCMacroOptionParser(String pattern, String nameExpression, String valueExpression) {
super(pattern, nameExpression, valueExpression);
}
@Override
public ICLanguageSettingEntry createEntry(String name, String value, int flag) {
return super.createEntry(name, unescapeString(value), flag);
}
}
private static class MSVCIncludePathOptionParser extends IncludePathOptionParser {
public MSVCIncludePathOptionParser(String pattern, String nameExpression) {
super(pattern, nameExpression);
}
@Override
public ICLanguageSettingEntry createEntry(String name, String value, int flag) {
return super.createEntry(name, unescapeString(value), flag);
}
}
private static class MSVCForceIncludePathOptionParser extends IncludeFileOptionParser {
public MSVCForceIncludePathOptionParser(String pattern, String nameExpression) {
super(pattern, nameExpression);
}
@Override
public ICLanguageSettingEntry createEntry(String name, String value, int flag) {
return super.createEntry(name, unescapeString(value), flag);
}
}
@SuppressWarnings("nls")
static final AbstractOptionParser[] includeOptionParsers = {
new MSVCIncludePathOptionParser("(-|/)I\\s*\"(.*)\"", "$2"),
new MSVCIncludePathOptionParser("(-|/)I\\s*([^\\s\"]*)", "$2"), };
@SuppressWarnings("nls")
static final AbstractOptionParser[] forceIncludeOptionParsers = {
new MSVCForceIncludePathOptionParser("(-|/)FI\\s*\"(.*)\"", "$2"),
new MSVCForceIncludePathOptionParser("(-|/)FI\\s*([^\\s\"]*)", "$2"), };
@SuppressWarnings("nls")
static final AbstractOptionParser[] msvcIncludeOptionParsers = {
new MSVCIncludePathOptionParser("(-|/)imsvc\\s*\"(.*)\"", "$2"),
new MSVCIncludePathOptionParser("(-|/)imsvc\\s*([^\\s\"]*)", "$2"), };
@SuppressWarnings("nls")
static final AbstractOptionParser[] clangISystemIncludeOptionParsers = {
new MSVCIncludePathOptionParser("(-|/)clang:-isystem\"(.*)\"", "$2"),
new MSVCIncludePathOptionParser("(-|/)clang:-isystem([^\\s\"]*)", "$2"), };
@SuppressWarnings("nls")
static final AbstractOptionParser[] defineOptionParsers = {
// /D "FOO=bar"
new MSVCMacroOptionParser("(-|/)D\\s*\"([^=]+)=(.*)\"", "$2", "$3"),
// /D FOO="bar"
new MSVCMacroOptionParser("(-|/)D\\s*([^\\s=\"]+)=\"(.*?)(?<!\\\\)\"", "$2", "$3"),
// /D FOO=bar
new MSVCMacroOptionParser("(-|/)D\\s*([^\\s=\"]+)=([^\\s\"][^\\s]*)?", "$2", "$3"),
// /D FOO
new MSVCMacroOptionParser("(-|/)D\\s*([^\\s=\"]+)", "$2", "1"),
// /D"FOO"
new MSVCMacroOptionParser("(-|/)D\\s*\"([^\\s=\"]+)\"", "$2", "1"), };
@SuppressWarnings("nls")
static final AbstractOptionParser[] undefineOptionParsers = {
// /U FOO
new MacroOptionParser("(-|/)U\\s*([^\\s=\"]+)", "$2", ICSettingEntry.UNDEFINED),
// /U "FOO"
new MacroOptionParser("(-|/)U\\s*\"(.*?)\"", "$2", ICSettingEntry.UNDEFINED) };
static final AbstractOptionParser[] emptyParsers = new AbstractOptionParser[0];
static final AbstractOptionParser[] optionParsers;
static {
List<AbstractOptionParser> parsers = new ArrayList<>(Arrays.asList(includeOptionParsers));
Collections.addAll(parsers, defineOptionParsers);
Collections.addAll(parsers, msvcIncludeOptionParsers);
Collections.addAll(parsers, clangISystemIncludeOptionParsers);
Collections.addAll(parsers, forceIncludeOptionParsers);
Collections.addAll(parsers, undefineOptionParsers);
optionParsers = parsers.toArray(new AbstractOptionParser[0]);
}
/**
* "foo" or "C:\foo\\"
*
* Using look-behind to resolve ambiguity with \" e.g. "\"in quotes\""
*/
private static final String QUOTE = "(\".*?(?<!\\\\)(\\\\\\\\)?\")"; //$NON-NLS-1$
private static final Pattern OPTIONS_PATTERN = Pattern.compile("(-|/)[^\\s\"\\\\]*(\\s*[^-/\\s\"\\\\]*(" + QUOTE //$NON-NLS-1$
+ "|" + "([^-/\\s][^\\s]+)))?"); //$NON-NLS-1$ //$NON-NLS-2$
private static final int OPTION_GROUP = 0;
@Override
protected List<String> parseOptions(String line) {
if (line == null || currentResource == null) {
return null;
}
List<String> options = new ArrayList<>();
Matcher optionMatcher = OPTIONS_PATTERN.matcher(line);
while (optionMatcher.find()) {
String option = optionMatcher.group(OPTION_GROUP);
if (option != null) {
options.add(option);
}
}
return options;
}
@Override
protected AbstractOptionParser[] getOptionParsers() {
return optionParsers;
}
@SuppressWarnings("nls")
@Override
protected AbstractOptionParser[] getOptionParsers(String optionToParse) {
if (optionToParse.length() <= 1) {
return emptyParsers;
}
// Skip - or /, we know it's there with the OPTIONS_PATTERN
String optionName = optionToParse.substring(1);
if (optionName.startsWith("I")) {
return includeOptionParsers;
}
if (optionName.startsWith("D")) {
return defineOptionParsers;
}
if (optionName.startsWith("imsvc")) {
return msvcIncludeOptionParsers;
}
if (optionName.startsWith("clang")) {
return clangISystemIncludeOptionParsers;
}
if (optionName.startsWith("FI")) {
return forceIncludeOptionParsers;
}
if (optionName.startsWith("U")) {
return undefineOptionParsers;
}
return emptyParsers;
}
@Override
public MSVCBuildCommandParser cloneShallow() throws CloneNotSupportedException {
return (MSVCBuildCommandParser) super.cloneShallow();
}
@Override
public MSVCBuildCommandParser clone() throws CloneNotSupportedException {
return (MSVCBuildCommandParser) super.clone();
}
}