mirror of
https://github.com/eclipse-cdt/cdt
synced 2025-04-23 22:52:11 +02:00
Scanner discovery for CMake projects.
Reads the compile_commands.json file and feeds the commands into the processLine method of the build config which creates the scanner info. The scanner info is cached in memory and stored in the metadata directory. Change-Id: I8b04e661dfe767904d1c10119c07167fee8cd7e4
This commit is contained in:
parent
955c971682
commit
6c6901547e
12 changed files with 728 additions and 123 deletions
|
@ -16,7 +16,6 @@ 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;
|
||||
|
@ -27,8 +26,11 @@ 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.dom.ast.gnu.c.GCCLanguage;
|
||||
import org.eclipse.cdt.core.dom.ast.gnu.cpp.GPPLanguage;
|
||||
import org.eclipse.cdt.core.envvar.EnvironmentVariable;
|
||||
import org.eclipse.cdt.core.envvar.IEnvironmentVariable;
|
||||
import org.eclipse.cdt.core.model.ILanguage;
|
||||
import org.eclipse.cdt.core.parser.ExtendedScannerInfo;
|
||||
import org.eclipse.cdt.core.parser.IExtendedScannerInfo;
|
||||
import org.eclipse.core.resources.IBuildConfiguration;
|
||||
|
@ -57,8 +59,6 @@ public class GCCToolChain extends PlatformObject implements IToolChain {
|
|||
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);
|
||||
}
|
||||
|
@ -157,11 +157,12 @@ public class GCCToolChain extends PlatformObject implements IToolChain {
|
|||
}
|
||||
|
||||
@Override
|
||||
public IExtendedScannerInfo getScannerInfo(IBuildConfiguration buildConfig, Path command, String[] args,
|
||||
public IExtendedScannerInfo getScannerInfo(IBuildConfiguration buildConfig, List<String> commandStrings,
|
||||
IExtendedScannerInfo baseScannerInfo, IResource resource, URI buildDirectoryURI) {
|
||||
try {
|
||||
Path buildDirectory = Paths.get(buildDirectoryURI);
|
||||
|
||||
Path command = Paths.get(commandStrings.get(0));
|
||||
List<String> commandLine = new ArrayList<>();
|
||||
if (command.isAbsolute()) {
|
||||
commandLine.add(command.toString());
|
||||
|
@ -176,7 +177,7 @@ public class GCCToolChain extends PlatformObject implements IToolChain {
|
|||
}
|
||||
|
||||
addDiscoveryOptions(commandLine);
|
||||
commandLine.addAll(Arrays.asList(args));
|
||||
commandLine.addAll(commandStrings.subList(1, commandStrings.size()));
|
||||
|
||||
// Change output to stdout
|
||||
boolean haveOut = false;
|
||||
|
@ -225,6 +226,69 @@ public class GCCToolChain extends PlatformObject implements IToolChain {
|
|||
commandLine.add(tmpFile.toString());
|
||||
}
|
||||
|
||||
return getScannerInfo(buildConfig, commandLine, buildDirectory, tmpFile);
|
||||
} catch (IOException e) {
|
||||
Activator.log(e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public IExtendedScannerInfo getDefaultScannerInfo(IBuildConfiguration buildConfig,
|
||||
IExtendedScannerInfo baseScannerInfo, ILanguage language, URI buildDirectoryURI) {
|
||||
try {
|
||||
String[] commands = getCompileCommands(language);
|
||||
if (commands == null || commands.length == 0) {
|
||||
// no default commands
|
||||
return null;
|
||||
}
|
||||
|
||||
Path buildDirectory = Paths.get(buildDirectoryURI);
|
||||
|
||||
// Pick the first one
|
||||
Path command = Paths.get(commands[0]);
|
||||
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);
|
||||
|
||||
// output to stdout
|
||||
commandLine.add("-o"); //$NON-NLS-1$
|
||||
commandLine.add("-"); //$NON-NLS-1$
|
||||
|
||||
// Source is an empty tmp file
|
||||
String extension;
|
||||
if (GPPLanguage.ID.equals(language.getId())) {
|
||||
extension = ".cpp";
|
||||
} else if (GCCLanguage.ID.equals(language.getId())) {
|
||||
extension = ".c";
|
||||
} else {
|
||||
// In theory we shouldn't get here
|
||||
return null;
|
||||
}
|
||||
|
||||
Path tmpFile = Files.createTempFile(buildDirectory, ".sc", extension); //$NON-NLS-1$
|
||||
commandLine.add(tmpFile.toString());
|
||||
|
||||
return getScannerInfo(buildConfig, commandLine, buildDirectory, tmpFile);
|
||||
} catch (IOException e) {
|
||||
Activator.log(e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private IExtendedScannerInfo getScannerInfo(IBuildConfiguration buildConfig, List<String> commandLine,
|
||||
Path buildDirectory, Path tmpFile) throws IOException {
|
||||
Files.createDirectories(buildDirectory);
|
||||
|
||||
// Startup the command
|
||||
|
@ -266,10 +330,7 @@ public class GCCToolChain extends PlatformObject implements IToolChain {
|
|||
Files.delete(tmpFile);
|
||||
|
||||
return new ExtendedScannerInfo(symbols, includePath.toArray(new String[includePath.size()]));
|
||||
} catch (IOException e) {
|
||||
Activator.log(e);
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -331,32 +392,40 @@ public class GCCToolChain extends PlatformObject implements IToolChain {
|
|||
|
||||
@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;
|
||||
return new String[] { "gcc", "g++", "clang", "clang++", "cc", "c++" }; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ //$NON-NLS-6$
|
||||
}
|
||||
|
||||
@Override
|
||||
public IResource[] getResourcesFromCommand(String[] cmd, URI buildDirectoryURI) {
|
||||
public String[] getCompileCommands(ILanguage language) {
|
||||
if (GPPLanguage.ID.equals(language.getId())) {
|
||||
return new String[] { "g++", "clang++", "c++" }; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
|
||||
} else if (GCCLanguage.ID.equals(language.getId())) {
|
||||
return new String[] { "gcc", "clang", "cc" }; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
|
||||
} else {
|
||||
return new String[0];
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public IResource[] getResourcesFromCommand(List<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];
|
||||
for (int i = cmd.size() - 1; i >= 0; --i) {
|
||||
String arg = cmd.get(i);
|
||||
if (arg.startsWith("-")) { //$NON-NLS-1$
|
||||
// ran into an option, we're done.
|
||||
break;
|
||||
}
|
||||
for (IFile resource : root.findFilesForLocationURI(buildDirectoryURI.resolve(arg))) {
|
||||
Path srcPath = Paths.get(arg);
|
||||
URI uri;
|
||||
if (srcPath.isAbsolute()) {
|
||||
uri = srcPath.toUri();
|
||||
} else {
|
||||
uri = buildDirectoryURI.resolve(arg);
|
||||
}
|
||||
|
||||
for (IFile resource : root.findFilesForLocationURI(uri)) {
|
||||
resources.add(resource);
|
||||
}
|
||||
}
|
||||
|
@ -364,4 +433,22 @@ public class GCCToolChain extends PlatformObject implements IToolChain {
|
|||
return resources.toArray(new IResource[resources.size()]);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> stripCommand(List<String> command, IResource[] resources) {
|
||||
List<String> newCommand = new ArrayList<>();
|
||||
|
||||
for (int i = 0; i < command.size() - resources.length; ++i) {
|
||||
String arg = command.get(i);
|
||||
if (arg.startsWith("-o")) { //$NON-NLS-1$
|
||||
if (arg.equals("-o")) { //$NON-NLS-1$
|
||||
i++;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
newCommand.add(arg);
|
||||
}
|
||||
|
||||
return newCommand;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -10,7 +10,8 @@ Require-Bundle: org.eclipse.core.runtime,
|
|||
org.eclipse.debug.core;bundle-version="3.10.0",
|
||||
org.eclipse.launchbar.core;bundle-version="2.0.0",
|
||||
org.eclipse.cdt.core;bundle-version="5.12.0",
|
||||
org.eclipse.tools.templates.freemarker;bundle-version="1.0.0";visibility:=reexport
|
||||
org.eclipse.tools.templates.freemarker;bundle-version="1.0.0";visibility:=reexport,
|
||||
com.google.gson
|
||||
Bundle-RequiredExecutionEnvironment: JavaSE-1.8
|
||||
Bundle-ActivationPolicy: lazy
|
||||
Export-Package: org.eclipse.cdt.cmake.core
|
||||
|
|
|
@ -55,7 +55,9 @@ public class CMakeProjectGenerator extends FMProjectGenerator {
|
|||
// Create the source folders
|
||||
IProject project = getProject();
|
||||
List<IPathEntry> entries = new ArrayList<>();
|
||||
for (SourceRoot srcRoot : getManifest().getSrcRoots()) {
|
||||
List<SourceRoot> srcRoots = getManifest().getSrcRoots();
|
||||
if (srcRoots != null && !srcRoots.isEmpty()) {
|
||||
for (SourceRoot srcRoot : srcRoots) {
|
||||
IFolder sourceFolder = project.getFolder(srcRoot.getDir());
|
||||
if (!sourceFolder.exists()) {
|
||||
sourceFolder.create(true, true, monitor);
|
||||
|
@ -63,6 +65,9 @@ public class CMakeProjectGenerator extends FMProjectGenerator {
|
|||
|
||||
entries.add(CoreModel.newSourceEntry(sourceFolder.getFullPath()));
|
||||
}
|
||||
} else {
|
||||
entries.add(CoreModel.newSourceEntry(getProject().getFullPath()));
|
||||
}
|
||||
CoreModel.getDefault().create(project).setRawPathEntries(entries.toArray(new IPathEntry[entries.size()]),
|
||||
monitor);
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
package org.eclipse.cdt.cmake.core.internal;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileReader;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
|
@ -21,7 +22,6 @@ import org.eclipse.cdt.core.IConsoleParser;
|
|||
import org.eclipse.cdt.core.build.CBuildConfiguration;
|
||||
import org.eclipse.cdt.core.build.IToolChain;
|
||||
import org.eclipse.cdt.core.model.ICModelMarker;
|
||||
import org.eclipse.cdt.core.parser.IScannerInfo;
|
||||
import org.eclipse.cdt.core.resources.IConsole;
|
||||
import org.eclipse.core.resources.IBuildConfiguration;
|
||||
import org.eclipse.core.resources.IProject;
|
||||
|
@ -29,6 +29,8 @@ import org.eclipse.core.resources.IResource;
|
|||
import org.eclipse.core.runtime.CoreException;
|
||||
import org.eclipse.core.runtime.IProgressMonitor;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
|
||||
public class CMakeBuildConfiguration extends CBuildConfiguration {
|
||||
|
||||
public CMakeBuildConfiguration(IBuildConfiguration config, String name) throws CoreException {
|
||||
|
@ -72,6 +74,10 @@ public class CMakeBuildConfiguration extends CBuildConfiguration {
|
|||
}
|
||||
|
||||
project.refreshLocal(IResource.DEPTH_INFINITE, monitor);
|
||||
|
||||
// Load compile_commands.json file
|
||||
processCompileCommandsFile(monitor);
|
||||
|
||||
return new IProject[] { project };
|
||||
} catch (IOException e) {
|
||||
throw new CoreException(Activator.errorStatus(String.format("Building %s", project.getName()), e));
|
||||
|
@ -89,7 +95,7 @@ public class CMakeBuildConfiguration extends CBuildConfiguration {
|
|||
Path buildDir = getBuildDirectory();
|
||||
|
||||
if (!Files.exists(buildDir.resolve("Makefile"))) { //$NON-NLS-1$
|
||||
outStream.write("Makefile not found. Assuming clean");
|
||||
outStream.write("Makefile not found. Assuming clean.");
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -99,8 +105,6 @@ public class CMakeBuildConfiguration extends CBuildConfiguration {
|
|||
ProcessBuilder processBuilder = new ProcessBuilder(command).directory(buildDir.toFile());
|
||||
Process process = processBuilder.start();
|
||||
outStream.write(String.join(" ", command) + '\n'); //$NON-NLS-1$
|
||||
|
||||
// TODO error parsers
|
||||
watchProcess(process, new IConsoleParser[0], console);
|
||||
|
||||
project.refreshLocal(IResource.DEPTH_INFINITE, monitor);
|
||||
|
@ -109,10 +113,23 @@ public class CMakeBuildConfiguration extends CBuildConfiguration {
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public IScannerInfo getScannerInformation(IResource resource) {
|
||||
// TODO Auto-generated method stub
|
||||
return null;
|
||||
private void processCompileCommandsFile(IProgressMonitor monitor) throws CoreException {
|
||||
IProject project = getProject();
|
||||
Path commandsFile = getBuildDirectory().resolve("compile_commands.json"); //$NON-NLS-1$
|
||||
if (Files.exists(commandsFile)) {
|
||||
monitor.setTaskName("Processing compile_commands.json");
|
||||
try (FileReader reader = new FileReader(commandsFile.toFile())) {
|
||||
Gson gson = new Gson();
|
||||
CompileCommand[] commands = gson.fromJson(reader, CompileCommand[].class);
|
||||
for (CompileCommand command : commands) {
|
||||
processLine(command.getCommand());
|
||||
}
|
||||
shutdown();
|
||||
} catch (IOException e) {
|
||||
throw new CoreException(
|
||||
Activator.errorStatus(String.format("Processing compile commands %s", project.getName()), e));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,28 @@
|
|||
/*******************************************************************************
|
||||
* Copyright (c) 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.cmake.core.internal;
|
||||
|
||||
public class CompileCommand {
|
||||
|
||||
private String directory;
|
||||
private String command;
|
||||
private String file;
|
||||
|
||||
public String getDirectory() {
|
||||
return directory;
|
||||
}
|
||||
|
||||
public String getCommand() {
|
||||
return command;
|
||||
}
|
||||
|
||||
public String getFile() {
|
||||
return file;
|
||||
}
|
||||
|
||||
}
|
|
@ -2,4 +2,4 @@ cmake_minimum_required (VERSION 2.6)
|
|||
|
||||
project (${projectName})
|
||||
|
||||
add_executable(${projectName} src/${projectName}.cpp)
|
||||
add_executable(${projectName} ${projectName}.cpp)
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
<templateManifest>
|
||||
<srcRoot dir="src"/>
|
||||
<file src="/templates/simple/CMakeLists.txt"
|
||||
dest="/${projectName}/CMakeLists.txt"/>
|
||||
<file src="/templates/simple/main.cpp"
|
||||
dest="/${projectName}/src/${projectName}.cpp"
|
||||
dest="/${projectName}/${projectName}.cpp"
|
||||
open="true"/>
|
||||
</templateManifest>
|
|
@ -127,6 +127,7 @@ Require-Bundle: org.eclipse.cdt.core.native;bundle-version="[5.7.0,6.0.0)";visib
|
|||
org.eclipse.core.variables;bundle-version="[3.1.100,4.0.0)",
|
||||
org.eclipse.ltk.core.refactoring;bundle-version="3.4.0",
|
||||
org.eclipse.text;bundle-version="[3.2.0,4.0.0)",
|
||||
com.ibm.icu;bundle-version="4.4.2"
|
||||
com.ibm.icu;bundle-version="4.4.2",
|
||||
com.google.gson
|
||||
Bundle-ActivationPolicy: lazy
|
||||
Bundle-RequiredExecutionEnvironment: JavaSE-1.8
|
||||
|
|
|
@ -9,18 +9,24 @@ package org.eclipse.cdt.core.build;
|
|||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.File;
|
||||
import java.io.FileReader;
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.OutputStream;
|
||||
import java.io.PrintStream;
|
||||
import java.lang.reflect.Type;
|
||||
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.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
|
@ -30,14 +36,19 @@ import org.eclipse.cdt.core.IMarkerGenerator;
|
|||
import org.eclipse.cdt.core.ProblemMarkerInfo;
|
||||
import org.eclipse.cdt.core.envvar.IEnvironmentVariable;
|
||||
import org.eclipse.cdt.core.model.CoreModel;
|
||||
import org.eclipse.cdt.core.model.ICElement;
|
||||
import org.eclipse.cdt.core.model.ICModelMarker;
|
||||
import org.eclipse.cdt.core.model.ICProject;
|
||||
import org.eclipse.cdt.core.model.IOutputEntry;
|
||||
import org.eclipse.cdt.core.model.IPathEntry;
|
||||
import org.eclipse.cdt.core.model.ITranslationUnit;
|
||||
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.parser.IScannerInfoChangeListener;
|
||||
import org.eclipse.cdt.core.parser.IncludeExportPatterns;
|
||||
import org.eclipse.cdt.core.resources.IConsole;
|
||||
import org.eclipse.cdt.internal.core.parser.ParserSettings2;
|
||||
import org.eclipse.core.filesystem.URIUtil;
|
||||
import org.eclipse.core.resources.IBuildConfiguration;
|
||||
import org.eclipse.core.resources.IContainer;
|
||||
|
@ -58,6 +69,15 @@ import org.eclipse.osgi.util.NLS;
|
|||
import org.osgi.service.prefs.BackingStoreException;
|
||||
import org.osgi.service.prefs.Preferences;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonDeserializationContext;
|
||||
import com.google.gson.JsonDeserializer;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonParseException;
|
||||
|
||||
/**
|
||||
* Root class for CDT build configurations. Provides access to the build
|
||||
* settings for subclasses.
|
||||
|
@ -72,10 +92,15 @@ public abstract class CBuildConfiguration extends PlatformObject
|
|||
private static final String TOOLCHAIN_ID = "cdt.toolChain.id"; //$NON-NLS-1$
|
||||
private static final String TOOLCHAIN_VERSION = "cdt.toolChain.version"; //$NON-NLS-1$
|
||||
|
||||
private static final List<String> DEFAULT_COMMAND = new ArrayList<>(0);
|
||||
|
||||
private final String name;
|
||||
private final IBuildConfiguration config;
|
||||
private final IToolChain toolChain;
|
||||
|
||||
private final Map<IResource, List<IScannerInfoChangeListener>> scannerInfoListeners = new HashMap<>();
|
||||
private ScannerInfoCache scannerInfoCache;
|
||||
|
||||
protected CBuildConfiguration(IBuildConfiguration config, String name) throws CoreException {
|
||||
this.config = config;
|
||||
this.name = name;
|
||||
|
@ -98,7 +123,6 @@ public abstract class CBuildConfiguration extends PlatformObject
|
|||
String.format("Toolchain missing for config: %s", config.getName())));
|
||||
}
|
||||
}
|
||||
|
||||
toolChain = tc;
|
||||
}
|
||||
|
||||
|
@ -374,56 +398,182 @@ public abstract class CBuildConfiguration extends PlatformObject
|
|||
|
||||
}
|
||||
|
||||
private Map<IResource, IExtendedScannerInfo> cheaterInfo;
|
||||
private boolean infoChanged = false;
|
||||
|
||||
private void initScannerInfo() {
|
||||
if (cheaterInfo == null) {
|
||||
cheaterInfo = new HashMap<>();
|
||||
private File getScannerInfoCacheFile() {
|
||||
return CCorePlugin.getDefault().getStateLocation().append("infoCache") //$NON-NLS-1$
|
||||
.append(getProject().getName()).append(name + ".json").toFile(); //$NON-NLS-1$
|
||||
}
|
||||
|
||||
private static class IExtendedScannerInfoCreator implements JsonDeserializer<IExtendedScannerInfo> {
|
||||
@Override
|
||||
public IExtendedScannerInfo deserialize(JsonElement element, Type arg1,
|
||||
JsonDeserializationContext arg2) throws JsonParseException {
|
||||
JsonObject infoObj = element.getAsJsonObject();
|
||||
|
||||
Map<String, String> definedSymbols = null;
|
||||
if (infoObj.has("definedSymbols")) { //$NON-NLS-1$
|
||||
JsonObject definedSymbolsObj = infoObj.get("definedSymbols").getAsJsonObject(); //$NON-NLS-1$
|
||||
definedSymbols = new HashMap<>();
|
||||
for (Entry<String, JsonElement> entry : definedSymbolsObj.entrySet()) {
|
||||
definedSymbols.put(entry.getKey(), entry.getValue().getAsString());
|
||||
}
|
||||
}
|
||||
|
||||
String[] includePaths = null;
|
||||
if (infoObj.has("includePaths")) { //$NON-NLS-1$
|
||||
JsonArray includePathsArray = infoObj.get("includePaths").getAsJsonArray(); //$NON-NLS-1$
|
||||
List<String> includePathsList = new ArrayList<>(includePathsArray.size());
|
||||
for (Iterator<JsonElement> i = includePathsArray.iterator(); i.hasNext();) {
|
||||
includePathsList.add(i.next().getAsString());
|
||||
}
|
||||
includePaths = includePathsList.toArray(new String[includePathsList.size()]);
|
||||
}
|
||||
|
||||
IncludeExportPatterns includeExportPatterns = null;
|
||||
if (infoObj.has("includeExportPatterns")) { //$NON-NLS-1$
|
||||
JsonObject includeExportPatternsObj = infoObj.get("includeExportPatterns").getAsJsonObject(); //$NON-NLS-1$
|
||||
String exportPattern = null;
|
||||
if (includeExportPatternsObj.has("includeExportPattern")) { //$NON-NLS-1$
|
||||
exportPattern = includeExportPatternsObj.get("includeExportPattern") //$NON-NLS-1$
|
||||
.getAsJsonObject().get("pattern").getAsString(); //$NON-NLS-1$
|
||||
}
|
||||
|
||||
String beginExportsPattern = null;
|
||||
if (includeExportPatternsObj.has("includeBeginExportPattern")) { //$NON-NLS-1$
|
||||
beginExportsPattern = includeExportPatternsObj.get("includeBeginExportPattern") //$NON-NLS-1$
|
||||
.getAsJsonObject().get("pattern").getAsString(); //$NON-NLS-1$
|
||||
}
|
||||
|
||||
String endExportsPattern = null;
|
||||
if (includeExportPatternsObj.has("includeEndExportPattern")) { //$NON-NLS-1$
|
||||
endExportsPattern = includeExportPatternsObj.get("includeEndExportPattern") //$NON-NLS-1$
|
||||
.getAsJsonObject().get("pattern").getAsString(); //$NON-NLS-1$
|
||||
}
|
||||
|
||||
includeExportPatterns = new IncludeExportPatterns(exportPattern, beginExportsPattern,
|
||||
endExportsPattern);
|
||||
}
|
||||
|
||||
ExtendedScannerInfo info = new ExtendedScannerInfo(definedSymbols, includePaths);
|
||||
info.setIncludeExportPatterns(includeExportPatterns);
|
||||
info.setParserSettings(new ParserSettings2());
|
||||
return info;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 6.1
|
||||
*/
|
||||
protected void loadScannerInfoCache() {
|
||||
if (scannerInfoCache == null) {
|
||||
File cacheFile = getScannerInfoCacheFile();
|
||||
if (cacheFile.exists()) {
|
||||
try (FileReader reader = new FileReader(cacheFile)) {
|
||||
GsonBuilder gsonBuilder = new GsonBuilder();
|
||||
gsonBuilder.registerTypeAdapter(IExtendedScannerInfo.class,
|
||||
new IExtendedScannerInfoCreator());
|
||||
Gson gson = gsonBuilder.create();
|
||||
scannerInfoCache = gson.fromJson(reader, ScannerInfoCache.class);
|
||||
} catch (IOException e) {
|
||||
CCorePlugin.log(e);
|
||||
scannerInfoCache = new ScannerInfoCache();
|
||||
}
|
||||
} else {
|
||||
scannerInfoCache = new ScannerInfoCache();
|
||||
}
|
||||
scannerInfoCache.initCache();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 6.1
|
||||
*/
|
||||
protected void saveScannerInfoCache() {
|
||||
File cacheFile = getScannerInfoCacheFile();
|
||||
if (!cacheFile.getParentFile().exists()) {
|
||||
try {
|
||||
Files.createDirectories(cacheFile.getParentFile().toPath());
|
||||
} catch (IOException e) {
|
||||
CCorePlugin.log(e);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
try (FileWriter writer = new FileWriter(getScannerInfoCacheFile())) {
|
||||
Gson gson = new Gson();
|
||||
gson.toJson(scannerInfoCache, writer);
|
||||
} catch (IOException e) {
|
||||
CCorePlugin.log(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 6.1
|
||||
*/
|
||||
protected ScannerInfoCache getScannerInfoCache() {
|
||||
return scannerInfoCache;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IScannerInfo getScannerInformation(IResource resource) {
|
||||
initScannerInfo();
|
||||
return cheaterInfo.get(resource);
|
||||
loadScannerInfoCache();
|
||||
IExtendedScannerInfo info = scannerInfoCache.getScannerInfo(resource);
|
||||
if (info == null) {
|
||||
ICElement celement = CCorePlugin.getDefault().getCoreModel().create(resource);
|
||||
if (celement instanceof ITranslationUnit) {
|
||||
ITranslationUnit tu = (ITranslationUnit) celement;
|
||||
try {
|
||||
info = getToolChain().getDefaultScannerInfo(getBuildConfiguration(), null,
|
||||
tu.getLanguage(), getBuildDirectoryURI());
|
||||
scannerInfoCache.addScannerInfo(DEFAULT_COMMAND, info, resource);
|
||||
saveScannerInfoCache();
|
||||
} catch (CoreException e) {
|
||||
CCorePlugin.log(e.getStatus());
|
||||
}
|
||||
}
|
||||
}
|
||||
return info;
|
||||
}
|
||||
|
||||
private boolean infoChanged = false;
|
||||
|
||||
@Override
|
||||
public boolean processLine(String line) {
|
||||
// TODO smarter line parsing to deal with quoted arguments
|
||||
String[] command = line.split("\\s+"); //$NON-NLS-1$
|
||||
List<String> command = Arrays.asList(line.split("\\s+")); //$NON-NLS-1$
|
||||
|
||||
// Make sure it's a compile command
|
||||
boolean found = false;
|
||||
String[] compileCommands = toolChain.getCompileCommands();
|
||||
loop:
|
||||
for (String arg : command) {
|
||||
if (arg.startsWith("-")) { //$NON-NLS-1$
|
||||
// option found, missed our command
|
||||
break;
|
||||
return false;
|
||||
}
|
||||
|
||||
for (String cc : compileCommands) {
|
||||
if (arg.equals(cc)) {
|
||||
found = true;
|
||||
break;
|
||||
if (arg.endsWith(cc)
|
||||
&& (arg.equals(cc) || arg.endsWith("/" + cc) || arg.endsWith("\\" + cc))) { //$NON-NLS-1$ //$NON-NLS-2$
|
||||
break loop;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!found) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
IResource[] resources = toolChain.getResourcesFromCommand(command, getBuildDirectoryURI());
|
||||
if (resources != null) {
|
||||
List<String> commandStrings = toolChain.stripCommand(command, resources);
|
||||
|
||||
for (IResource resource : resources) {
|
||||
initScannerInfo();
|
||||
cheaterInfo.put(resource,
|
||||
getToolChain().getScannerInfo(getBuildConfiguration(), findCommand(command[0]),
|
||||
Arrays.copyOfRange(command, 1, command.length), null, resource,
|
||||
getBuildDirectoryURI()));
|
||||
loadScannerInfoCache();
|
||||
if (scannerInfoCache.hasCommand(commandStrings)) {
|
||||
scannerInfoCache.addResource(commandStrings, resource);
|
||||
} else {
|
||||
Path commandPath = findCommand(command.get(0));
|
||||
command.set(0, commandPath.toString());
|
||||
IExtendedScannerInfo info = getToolChain().getScannerInfo(getBuildConfiguration(),
|
||||
command, null, resource, getBuildDirectoryURI());
|
||||
scannerInfoCache.addScannerInfo(commandStrings, info, resource);
|
||||
}
|
||||
infoChanged = true;
|
||||
}
|
||||
return true;
|
||||
|
@ -441,7 +591,9 @@ public abstract class CBuildConfiguration extends PlatformObject
|
|||
// TODO persist changes
|
||||
|
||||
// Trigger a reindex if anything changed
|
||||
// TODO be more surgical
|
||||
if (infoChanged) {
|
||||
saveScannerInfoCache();
|
||||
CCorePlugin.getIndexManager().reindex(CoreModel.getDefault().create(getProject()));
|
||||
infoChanged = false;
|
||||
}
|
||||
|
@ -449,12 +601,23 @@ public abstract class CBuildConfiguration extends PlatformObject
|
|||
|
||||
@Override
|
||||
public void subscribe(IResource resource, IScannerInfoChangeListener listener) {
|
||||
// TODO for IScannerInfoProvider
|
||||
List<IScannerInfoChangeListener> listeners = scannerInfoListeners.get(resource);
|
||||
if (listeners == null) {
|
||||
listeners = new ArrayList<>();
|
||||
scannerInfoListeners.put(resource, listeners);
|
||||
}
|
||||
listeners.add(listener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unsubscribe(IResource resource, IScannerInfoChangeListener listener) {
|
||||
// TODO for IScannerInfoProvider
|
||||
List<IScannerInfoChangeListener> listeners = scannerInfoListeners.get(resource);
|
||||
if (listeners != null) {
|
||||
listeners.remove(listener);
|
||||
if (listeners.isEmpty()) {
|
||||
scannerInfoListeners.remove(resource);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -9,8 +9,12 @@ package org.eclipse.cdt.core.build;
|
|||
|
||||
import java.net.URI;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import org.eclipse.cdt.core.envvar.IEnvironmentVariable;
|
||||
import org.eclipse.cdt.core.model.ILanguage;
|
||||
import org.eclipse.cdt.core.parser.IExtendedScannerInfo;
|
||||
import org.eclipse.core.resources.IBuildConfiguration;
|
||||
import org.eclipse.core.resources.IResource;
|
||||
|
@ -24,17 +28,48 @@ import org.eclipse.core.runtime.IAdaptable;
|
|||
*/
|
||||
public interface IToolChain extends IAdaptable {
|
||||
|
||||
// Standard attributes
|
||||
/**
|
||||
* Property: The OS the toolchain builds for.
|
||||
*/
|
||||
static final String ATTR_OS = "os"; //$NON-NLS-1$
|
||||
|
||||
/**
|
||||
* Property: The CPU architecture the toolchain supports.
|
||||
*/
|
||||
static final String ATTR_ARCH = "arch"; //$NON-NLS-1$
|
||||
|
||||
/**
|
||||
* Property: A package ID to reflect different version/package line up of
|
||||
* the platform this toolchain supports.
|
||||
*/
|
||||
static final String ATTR_PACKAGE = "package"; //$NON-NLS-1$
|
||||
|
||||
/**
|
||||
* The provider of the toolchain.
|
||||
*
|
||||
* @return toolchain provider
|
||||
*/
|
||||
IToolChainProvider getProvider();
|
||||
|
||||
/**
|
||||
* The ID of the toolchain
|
||||
*
|
||||
* @return toolchain ID
|
||||
*/
|
||||
String getId();
|
||||
|
||||
/**
|
||||
* The version of the toolchain
|
||||
*
|
||||
* @return toolchain version
|
||||
*/
|
||||
String getVersion();
|
||||
|
||||
/**
|
||||
* The user friendly name for the toolchain
|
||||
*
|
||||
* @return toolchain name
|
||||
*/
|
||||
String getName();
|
||||
|
||||
/**
|
||||
|
@ -48,23 +83,165 @@ public interface IToolChain extends IAdaptable {
|
|||
*/
|
||||
String getProperty(String key);
|
||||
|
||||
/**
|
||||
* Set a property on the toolchain.
|
||||
*
|
||||
* @param key
|
||||
* key of the property
|
||||
* @param value
|
||||
* value of the property
|
||||
*/
|
||||
void setProperty(String key, String value);
|
||||
|
||||
IEnvironmentVariable getVariable(String name);
|
||||
|
||||
/**
|
||||
* Return the environment variables to be set when invoking the tools in the
|
||||
* toolchain.
|
||||
*
|
||||
* @return environment variables
|
||||
*/
|
||||
IEnvironmentVariable[] getVariables();
|
||||
|
||||
IExtendedScannerInfo getScannerInfo(IBuildConfiguration buildConfig, Path command, String[] args,
|
||||
IExtendedScannerInfo baseScannerInfo, IResource resource, URI buildDirectoryURI);
|
||||
/**
|
||||
* Return the environment variable of the given name used when invoking the
|
||||
* toolchain.
|
||||
*
|
||||
* @param name
|
||||
* environment variable name
|
||||
* @return environment variable value
|
||||
*/
|
||||
IEnvironmentVariable getVariable(String name);
|
||||
|
||||
/**
|
||||
* Returns the error parser IDs use to create error markers for builds with
|
||||
* this toolchain.
|
||||
*
|
||||
* @return error parser IDs
|
||||
*/
|
||||
String[] getErrorParserIds();
|
||||
|
||||
Path getCommandPath(Path command);
|
||||
|
||||
String[] getCompileCommands();
|
||||
|
||||
IResource[] getResourcesFromCommand(String[] command, URI buildDirectoryURI);
|
||||
|
||||
/**
|
||||
* Returns the IDs for the binary parsers that can parse the build output of
|
||||
* the toolchain.
|
||||
*
|
||||
* @return binary parser IDs for this toolchain
|
||||
*/
|
||||
String getBinaryParserId();
|
||||
|
||||
/**
|
||||
* Get the scanner info for a given build config, command, base scanner
|
||||
* info, resource and build directory.
|
||||
*
|
||||
* @param buildConfig
|
||||
* the build configuration this scanner info applies to
|
||||
* @param command
|
||||
* the compile command that is used to build the resource
|
||||
* @param baseScannerInfo
|
||||
* base scanner info that this scanner info extends/replaces
|
||||
* @param resource
|
||||
* the resource this scanner info applies to, usually a source
|
||||
* file
|
||||
* @param buildDirectoryURI
|
||||
* where the build command is run to build this resource
|
||||
* @return scanner info for this resource
|
||||
* @since 6.1
|
||||
*/
|
||||
default IExtendedScannerInfo getScannerInfo(IBuildConfiguration buildConfig, List<String> command,
|
||||
IExtendedScannerInfo baseScannerInfo, IResource resource, URI buildDirectoryURI) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
default IExtendedScannerInfo getScannerInfo(IBuildConfiguration buildConfig, Path command, String[] args,
|
||||
IExtendedScannerInfo baseScannerInfo, IResource resource, URI buildDirectoryURI) {
|
||||
List<String> commandStrings = new ArrayList<>(args.length + 1);
|
||||
commandStrings.add(command.toString());
|
||||
commandStrings.addAll(Arrays.asList(args));
|
||||
return getScannerInfo(buildConfig, commandStrings, baseScannerInfo, resource, buildDirectoryURI);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the default scanner info for this toolchain. This is used before
|
||||
* any build information is available to provide at least a minimal scanner
|
||||
* info based on the compiler built-ins.
|
||||
*
|
||||
* @param buildConfig
|
||||
* the build configuration this scanner info applies to
|
||||
* @param baseScannerInfo
|
||||
* base scanner info that this scanner info extends/replaces
|
||||
* @param language
|
||||
* the source language that selects the tool to provide scanner
|
||||
* info for
|
||||
* @param buildDirectoryURI
|
||||
* the build directory that would be used to run commands
|
||||
* @returns default scanner info for this language
|
||||
* @since 6.1
|
||||
*/
|
||||
default IExtendedScannerInfo getDefaultScannerInfo(IBuildConfiguration buildConfig,
|
||||
IExtendedScannerInfo baseScannerInfo, ILanguage language, URI buildDirectoryURI) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the absolute path of the tool represented by the command
|
||||
*
|
||||
* @param command
|
||||
* the command as it usually appears on the command line
|
||||
* @return the absolute path to the tool for the command
|
||||
*/
|
||||
Path getCommandPath(Path command);
|
||||
|
||||
/**
|
||||
* Returns the list of compiler tools.
|
||||
*
|
||||
* @return list of compiler tools
|
||||
*/
|
||||
String[] getCompileCommands();
|
||||
|
||||
/**
|
||||
* Returns the list of compiler tools for a given language.
|
||||
*
|
||||
* @param language
|
||||
* the language for the commands
|
||||
* @return the compile commands for the language
|
||||
* @since 6.1
|
||||
*/
|
||||
default String[] getCompileCommands(ILanguage language) {
|
||||
return new String[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the list of resources referenced in a compile command.
|
||||
*
|
||||
* @param command
|
||||
* the compile command
|
||||
* @param buildDirectoryURI
|
||||
* the directory the compile command runs in
|
||||
* @return the list of resources referenced in the compile command
|
||||
* @since 6.1
|
||||
*/
|
||||
default IResource[] getResourcesFromCommand(List<String> command, URI buildDirectoryURI) {
|
||||
return new IResource[0];
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
default IResource[] getResourcesFromCommand(String[] command, URI buildDirectoryURI) {
|
||||
return getResourcesFromCommand(Arrays.asList(command), buildDirectoryURI);
|
||||
}
|
||||
|
||||
/**
|
||||
* Strips the resources from the compile command. Use to produce the common
|
||||
* parts of the command shared by a number of resources.
|
||||
*
|
||||
* @param command
|
||||
* the original compile command
|
||||
* @param resources
|
||||
* the resources this command compiles for usually returned by
|
||||
* getResourcesFromCommand()
|
||||
* @return the stripped command
|
||||
* @since 6.1
|
||||
*/
|
||||
default List<String> stripCommand(List<String> command, IResource[] resources) {
|
||||
return command;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,126 @@
|
|||
/*******************************************************************************
|
||||
* Copyright (c) 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.core.build;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.eclipse.cdt.core.parser.IExtendedScannerInfo;
|
||||
import org.eclipse.core.resources.IResource;
|
||||
|
||||
/**
|
||||
* Scanner info for a given build configuration.
|
||||
*
|
||||
* @since 6.1
|
||||
*/
|
||||
public class ScannerInfoCache {
|
||||
|
||||
private static class Command {
|
||||
public List<String> command;
|
||||
public IExtendedScannerInfo info;
|
||||
public List<String> resourcePaths;
|
||||
}
|
||||
|
||||
private List<Command> commands;
|
||||
|
||||
private transient Map<List<String>, Command> commandMap = new HashMap<>();
|
||||
private transient Map<String, Command> resourceMap = new HashMap<>();
|
||||
|
||||
/**
|
||||
* Initialize the cache of scanner info. Call this after loading this info
|
||||
* using Gson.
|
||||
*/
|
||||
public void initCache() {
|
||||
if (commands == null) {
|
||||
commands = new ArrayList<>();
|
||||
}
|
||||
|
||||
for (Command command : commands) {
|
||||
commandMap.put(command.command, command);
|
||||
for (String resourcePath : command.resourcePaths) {
|
||||
resourceMap.put(resourcePath, command);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public IExtendedScannerInfo getScannerInfo(IResource resource) {
|
||||
String resourcePath = resource.getLocation().toOSString();
|
||||
Command command = resourceMap.get(resourcePath);
|
||||
return command != null ? command.info : null;
|
||||
}
|
||||
|
||||
public IExtendedScannerInfo getScannerInfo(List<String> commandStrings) {
|
||||
Command command = commandMap.get(commandStrings);
|
||||
return command != null ? command.info : null;
|
||||
}
|
||||
|
||||
public boolean hasCommand(List<String> commandStrings) {
|
||||
return commandMap.get(commandStrings) != null;
|
||||
}
|
||||
|
||||
public void addScannerInfo(List<String> commandStrings, IExtendedScannerInfo info, IResource resource) {
|
||||
// Do I need to remove the resource from an existing command?
|
||||
String resourcePath = resource.getLocation().toOSString();
|
||||
Command oldCommand = resourceMap.get(resourcePath);
|
||||
if (oldCommand != null) {
|
||||
if (oldCommand.command.equals(commandStrings)) {
|
||||
// duplicate
|
||||
return;
|
||||
} else {
|
||||
oldCommand.resourcePaths.remove(resourcePath);
|
||||
if (oldCommand.resourcePaths.isEmpty()) {
|
||||
// unused, remove
|
||||
commandMap.remove(commandStrings);
|
||||
commands.remove(oldCommand);
|
||||
resourceMap.remove(resourcePath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Command command = commandMap.get(commandStrings);
|
||||
if (command != null) {
|
||||
command.info = info;
|
||||
command.resourcePaths.add(resourcePath);
|
||||
resourceMap.put(resourcePath, command);
|
||||
} else {
|
||||
command = new Command();
|
||||
command.command = commandStrings;
|
||||
command.info = info;
|
||||
command.resourcePaths = new ArrayList<>();
|
||||
command.resourcePaths.add(resourcePath);
|
||||
commands.add(command);
|
||||
commandMap.put(commandStrings, command);
|
||||
resourceMap.put(resourcePath, command);
|
||||
}
|
||||
}
|
||||
|
||||
public void addResource(List<String> commandStrings, IResource resource) {
|
||||
String resourcePath = resource.getLocation().toOSString();
|
||||
Command command = commandMap.get(commandStrings);
|
||||
Command current = resourceMap.get(resourcePath);
|
||||
if (current != null) {
|
||||
if (!current.equals(command)) {
|
||||
// remove from old command
|
||||
current.resourcePaths.remove(resourcePath);
|
||||
if (current.resourcePaths.isEmpty()) {
|
||||
commands.remove(current);
|
||||
commandMap.remove(current.command);
|
||||
}
|
||||
} else {
|
||||
// we're already there
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
command.resourcePaths.add(resource.getLocation().toOSString());
|
||||
resourceMap.put(resourcePath, command);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -177,6 +177,7 @@ Java and all Java-based trademarks are trademarks of Oracle Corporation in the U
|
|||
</license>
|
||||
|
||||
<plugins>
|
||||
<plugin id="com.google.gson"/>
|
||||
<plugin id="com.ibm.icu"/>
|
||||
<plugin id="javax.annotation"/>
|
||||
<plugin id="javax.el"/>
|
||||
|
|
Loading…
Add table
Reference in a new issue