diff options
author | David Kaspar | 2013-11-28 18:58:37 +0000 |
---|---|---|
committer | Doug Schaefer | 2013-11-29 04:33:42 +0000 |
commit | 026b9325f08f899d0b770b2e0968446599144c74 (patch) | |
tree | 93a31b5d0579f71ef479e24868bb1732d73d7056 | |
parent | f887c8e671725af7d3f18ed55d65d2235036fa5b (diff) | |
download | org.eclipse.cdt-026b9325f08f899d0b770b2e0968446599144c74.tar.gz org.eclipse.cdt-026b9325f08f899d0b770b2e0968446599144c74.tar.xz org.eclipse.cdt-026b9325f08f899d0b770b2e0968446599144c74.zip |
Bug 422797 - API for retrieving QMake information from Qt project
API is located at org.eclipse.cdt.qt.core.index package.
Entry point is QMakeProjectInfoFactory.getForActiveConfigurationIn method
that provides ability to retrieve QMake information (IQMakeInfo interface)
for active project configuration of a specified project.
Also allows to listen on changes of such information.
qmakeEnvProvider extensions allows CDT build-system to provide environment
for QMake runs within their build-system.
Information is gather by parsing output of:
1) qmake -query
2) qmake -E file.pro // only for QMake version 3.0
Change-Id: Iae569bdbc89dc26d60530596b66b5227f36dfae6
Signed-off-by: David Kaspar <dkaspar@blackberry.com>
Reviewed-on: https://git.eclipse.org/r/19082
Reviewed-by: Andrew Eidsness <eclipse@jfront.com>
Tested-by: Hudson CI
Reviewed-by: Doug Schaefer <dschaefer@qnx.com>
IP-Clean: Doug Schaefer <dschaefer@qnx.com>
21 files changed, 1730 insertions, 6 deletions
diff --git a/qt/org.eclipse.cdt.qt.core/META-INF/MANIFEST.MF b/qt/org.eclipse.cdt.qt.core/META-INF/MANIFEST.MF index 31d4aeefd56..bb85c8e693d 100644 --- a/qt/org.eclipse.cdt.qt.core/META-INF/MANIFEST.MF +++ b/qt/org.eclipse.cdt.qt.core/META-INF/MANIFEST.MF @@ -7,8 +7,10 @@ Bundle-Activator: org.eclipse.cdt.qt.core.QtPlugin Bundle-Vendor: Eclipse CDT Require-Bundle: org.eclipse.core.runtime, org.eclipse.core.resources, + org.eclipse.core.expressions;bundle-version="[3.2.0,4.0.0)", org.eclipse.cdt.core Bundle-RequiredExecutionEnvironment: JavaSE-1.6 Bundle-ActivationPolicy: lazy Bundle-Localization: plugin -Export-Package: org.eclipse.cdt.qt.core +Export-Package: org.eclipse.cdt.qt.core, + org.eclipse.cdt.qt.core.index diff --git a/qt/org.eclipse.cdt.qt.core/build.properties b/qt/org.eclipse.cdt.qt.core/build.properties index 09a194a7e88..d8b18e5db46 100644 --- a/qt/org.eclipse.cdt.qt.core/build.properties +++ b/qt/org.eclipse.cdt.qt.core/build.properties @@ -5,4 +5,5 @@ bin.includes = META-INF/,\ plugin.xml,\ templates/,\ about.html -src.includes = about.html +src.includes = about.html,\ + schema/ diff --git a/qt/org.eclipse.cdt.qt.core/plugin.properties b/qt/org.eclipse.cdt.qt.core/plugin.properties index 0206fd12b0a..9f9360a2955 100644 --- a/qt/org.eclipse.cdt.qt.core/plugin.properties +++ b/qt/org.eclipse.cdt.qt.core/plugin.properties @@ -1,2 +1,3 @@ qtProjectFile.name = Qt Project File qmlFile.name = QML File +qmakeEnvProvider.name = QMake Environment Provider diff --git a/qt/org.eclipse.cdt.qt.core/plugin.xml b/qt/org.eclipse.cdt.qt.core/plugin.xml index d31e961af1d..eb99e065f65 100644 --- a/qt/org.eclipse.cdt.qt.core/plugin.xml +++ b/qt/org.eclipse.cdt.qt.core/plugin.xml @@ -1,6 +1,8 @@ <?xml version="1.0" encoding="UTF-8"?> <?eclipse version="3.4"?> <plugin> + <extension-point id="qmakeEnvProvider" name="%qmakeEnvProvider.name" schema="schema/qmakeEnvProvider.exsd"/> + <extension point="org.eclipse.cdt.core.templates"> <template diff --git a/qt/org.eclipse.cdt.qt.core/schema/qmakeEnvProvider.exsd b/qt/org.eclipse.cdt.qt.core/schema/qmakeEnvProvider.exsd new file mode 100644 index 00000000000..2c6dfad437b --- /dev/null +++ b/qt/org.eclipse.cdt.qt.core/schema/qmakeEnvProvider.exsd @@ -0,0 +1,136 @@ +<?xml version='1.0' encoding='UTF-8'?> +<!-- Schema file written by PDE --> +<schema targetNamespace="org.eclipse.cdt.qt.core" xmlns="http://www.w3.org/2001/XMLSchema"> +<annotation> + <appinfo> + <meta.schema plugin="org.eclipse.cdt.qt.core" id="qmakeEnvProvider" name="QMake Environment Provider"/> + </appinfo> + <documentation> + This extension point allows to provide environment for qmake cmd-line tool execution. + </documentation> + </annotation> + + <include schemaLocation="schema://org.eclipse.core.expressions/schema/expressionLanguage.exsd"/> + + <element name="extension"> + <annotation> + <appinfo> + <meta.element /> + </appinfo> + </annotation> + <complexType> + <sequence> + <element ref="qmakeEnvProvider" minOccurs="1" maxOccurs="unbounded"/> + </sequence> + <attribute name="point" type="string" use="required"> + <annotation> + <documentation> + a fully qualified identifier of the target extension point + </documentation> + </annotation> + </attribute> + <attribute name="id" type="string"> + <annotation> + <documentation> + an optional identifier of the extension instance + </documentation> + </annotation> + </attribute> + <attribute name="name" type="string"> + <annotation> + <documentation> + an optional name of the extension instance + </documentation> + <appinfo> + <meta.attribute translatable="true"/> + </appinfo> + </annotation> + </attribute> + </complexType> + </element> + + <element name="qmakeEnvProvider"> + <complexType> + <sequence> + <element ref="enablement" minOccurs="0" maxOccurs="1"/> + </sequence> + <attribute name="class" type="string" use="required"> + <annotation> + <documentation> + Implementation of QMake environment provider which is used to determinate environment for running QMake. + </documentation> + <appinfo> + <meta.attribute kind="java" basedOn=":org.eclipse.cdt.qt.core.index.IQMakeEnvProvider"/> + </appinfo> + </annotation> + </attribute> + <attribute name="priority" type="integer" default="0"> + <annotation> + <documentation> + Priority of QMake environment provider. Less number means higher priority. Default priority is 0. + </documentation> + </annotation> + </attribute> + </complexType> + </element> + + <annotation> + <appinfo> + <meta.section type="since"/> + </appinfo> + <documentation> + 8.2 + </documentation> + </annotation> + + <annotation> + <appinfo> + <meta.section type="examples"/> + </appinfo> + <documentation> + The following is an example of a qmakeEnvProvider contribution: +<p> +<pre> +<extension + point="org.eclipse.cdt.qt.core.qmakeEnvProvider" + id="example" + name="Example QMake Env Provider Extension"> + <qmakeEnvProvider + class="com.example.internal.ExampleProvider"> + <enablement> + <with variable="projectNatures"> + <iterate operator="or"> + <equals value="com.example.my-nature"/> + </iterate> + </with> + </enablement> + </qmakeEnvProvider> +</extension> +</pre> +</p> + </documentation> + </annotation> + + <annotation> + <appinfo> + <meta.section type="apiinfo"/> + </appinfo> + <documentation> + The contributed class must implement <code>org.eclipse.cdt.qt.core.index.IQMakeEnvProvider</code>. + </documentation> + </annotation> + + <annotation> + <appinfo> + <meta.section type="copyright"/> + </appinfo> + <documentation> + Copyright (c) 2013 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 + </documentation> + </annotation> + +</schema> diff --git a/qt/org.eclipse.cdt.qt.core/src/org/eclipse/cdt/internal/qt/core/index/QMakeEnvProviderDescriptor.java b/qt/org.eclipse.cdt.qt.core/src/org/eclipse/cdt/internal/qt/core/index/QMakeEnvProviderDescriptor.java new file mode 100644 index 00000000000..63f6b4aedfd --- /dev/null +++ b/qt/org.eclipse.cdt.qt.core/src/org/eclipse/cdt/internal/qt/core/index/QMakeEnvProviderDescriptor.java @@ -0,0 +1,146 @@ +/* + * Copyright (c) 2013 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.index; + +import java.util.Arrays; +import java.util.concurrent.atomic.AtomicReference; + +import org.eclipse.cdt.core.settings.model.ICConfigurationDescription; +import org.eclipse.cdt.qt.core.QtPlugin; +import org.eclipse.cdt.qt.core.index.IQMakeEnv; +import org.eclipse.cdt.qt.core.index.IQMakeEnvProvider; +import org.eclipse.cdt.qt.core.index.IQMakeEnvProvider.IController; +import org.eclipse.core.expressions.EvaluationContext; +import org.eclipse.core.expressions.EvaluationResult; +import org.eclipse.core.expressions.Expression; +import org.eclipse.core.expressions.ExpressionConverter; +import org.eclipse.core.expressions.ExpressionTagNames; +import org.eclipse.core.resources.IProject; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IConfigurationElement; + +/** + * Represents a IQMakeEnvProvider that is registered via qmakeEnvProvider extension point. + */ +public final class QMakeEnvProviderDescriptor implements Comparable<QMakeEnvProviderDescriptor> { + + private static final String ATTR_CLASS = "class"; //$NON-NLS-1$ + private static final String ATTR_PRIORITY = "priority"; //$NON-NLS-1$ + private static final String VAR_PROJECTNATURES = "projectNatures"; //$NON-NLS-1$ + + private final IConfigurationElement element; + private final String id; + private final int priority; + private final AtomicReference<Boolean> evaluation = new AtomicReference<Boolean>(); + private final Expression enablementExpression; + + private IQMakeEnvProvider provider; + + QMakeEnvProviderDescriptor(IConfigurationElement element) { + this.element = element; + + id = element.getContributor().getName(); + + // parse priority + int prio = 0; + String priorityString = element.getAttribute(ATTR_PRIORITY); + if (priorityString != null) { + try { + prio = Integer.parseInt(priorityString); + } catch (NumberFormatException e) { + QtPlugin.log("Invalid priority value of " + id, e); //$NON-NLS-1$ + } + } + this.priority = prio; + + // parse enablement expression + Expression expr = null; + IConfigurationElement[] children = element.getChildren(ExpressionTagNames.ENABLEMENT); + switch (children.length) { + case 0: + evaluation.set(Boolean.TRUE); + break; + case 1: + try { + ExpressionConverter parser = ExpressionConverter.getDefault(); + expr = parser.perform(children[0]); + } catch (CoreException e) { + QtPlugin.log("Error in enablement expression of " + id, e); //$NON-NLS-1$ + } + break; + default: + QtPlugin.log("Too many enablement expressions for " + id); //$NON-NLS-1$ + evaluation.set(Boolean.FALSE); + break; + } + enablementExpression = expr; + } + + @Override + public int compareTo(QMakeEnvProviderDescriptor that) { + if (that == null) { + return -1; + } + return this.priority - that.priority; + } + + /** + * Used by QMakeEnvProviderManager to ask for creating a IQMakeEnv for a specific IQMakeEnvProvider.IController using the related IQMakeEnvProvider + * + * @param controller the controller + * @return the IQMakeEnv instance; or null if no instance is provided + */ + public IQMakeEnv createEnv(IController controller) { + if (! matches(controller)) { + return null; + } + if (provider == null) { + synchronized (this) { + if (provider == null) { + try { + provider = (IQMakeEnvProvider) element.createExecutableExtension(ATTR_CLASS); + } catch (CoreException e) { + QtPlugin.log("Error in class attribute of " + id, e); //$NON-NLS-1$ + return null; + } + } + } + } + return provider.createEnv(controller); + } + + /** + * Checks whether an enablement expression evaluation is true. + * + * @param controller the controller + * @return true if the provider can be used; false otherwise + */ + private boolean matches(IController controller) { + Boolean eval = evaluation.get(); + if (eval != null) { + return eval.booleanValue(); + } + if (enablementExpression != null) { + ICConfigurationDescription configuration = controller != null ? controller.getConfiguration() : null; + IProject project = configuration != null ? configuration.getProjectDescription().getProject() : null; + EvaluationContext evalContext = new EvaluationContext(null, project); + try { + if (project != null) { + String[] natures = project.getDescription().getNatureIds(); + evalContext.addVariable(VAR_PROJECTNATURES, Arrays.asList(natures)); + } + return enablementExpression.evaluate(evalContext) == EvaluationResult.TRUE; + } catch (CoreException e) { + QtPlugin.log("Error while evaluating enablement expression for " + id, e); //$NON-NLS-1$ + } + } + evaluation.set(Boolean.FALSE); + return false; + } + +} diff --git a/qt/org.eclipse.cdt.qt.core/src/org/eclipse/cdt/internal/qt/core/index/QMakeEnvProviderManager.java b/qt/org.eclipse.cdt.qt.core/src/org/eclipse/cdt/internal/qt/core/index/QMakeEnvProviderManager.java new file mode 100644 index 00000000000..f1758ef9348 --- /dev/null +++ b/qt/org.eclipse.cdt.qt.core/src/org/eclipse/cdt/internal/qt/core/index/QMakeEnvProviderManager.java @@ -0,0 +1,117 @@ +/* + * Copyright (c) 2013 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.index; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import org.eclipse.cdt.core.CCorePlugin; +import org.eclipse.cdt.core.envvar.IEnvironmentVariable; +import org.eclipse.cdt.core.settings.model.ICConfigurationDescription; +import org.eclipse.cdt.qt.core.QtPlugin; +import org.eclipse.cdt.qt.core.index.IQMakeEnv; +import org.eclipse.cdt.qt.core.index.IQMakeEnvProvider; +import org.eclipse.cdt.qt.core.index.QMakeEnvInfo; +import org.eclipse.core.resources.IFile; +import org.eclipse.core.resources.IProject; +import org.eclipse.core.runtime.IConfigurationElement; +import org.eclipse.core.runtime.Platform; + +/** + * Represents a manager of registered qmakeEnvProvider extensions. + */ +public final class QMakeEnvProviderManager { + + private static QMakeEnvProviderManager INSTANCE = new QMakeEnvProviderManager(); + + private final List<QMakeEnvProviderDescriptor> descriptors; + + public static QMakeEnvProviderManager getInstance() { + return INSTANCE; + } + + private QMakeEnvProviderManager() { + descriptors = loadDescriptors(); + } + + /** + * Returns a list of all registerd qmakeEnvProvider extensions. + * + * @return the list of extensions + */ + private static List<QMakeEnvProviderDescriptor> loadDescriptors() { + List<QMakeEnvProviderDescriptor> descriptors = new ArrayList<QMakeEnvProviderDescriptor>(); + IConfigurationElement[] elements = Platform.getExtensionRegistry() + .getConfigurationElementsFor(QtPlugin.ID, QtPlugin.QMAKE_ENV_PROVIDER_EXT_POINT_NAME); + for (IConfigurationElement element : elements) { + descriptors.add(new QMakeEnvProviderDescriptor(element)); + } + Collections.sort(descriptors); + return descriptors; + } + + /** + * Called by QMakeProjectInfo to create IQMakeEnv for a specified IController. + * It asks each qmakeEnvProvider extensions to provide the instance. If none of them provides, then the default IQMakeEnvProvider is returned. + * + * @param controller the controller + * @return the IQMakeEnv instance for specifying the QMake environment + */ + public IQMakeEnv createEnv(IQMakeEnvProvider.IController controller) { + for (QMakeEnvProviderDescriptor descriptor : descriptors) { + IQMakeEnv env = descriptor.createEnv(controller); + if (env != null) { + return env; + } + } + return new ConfigurationQMakeEnv(controller.getConfiguration()); + } + + /** + * Represents a fallback IQMakeEnvProvider that is used for a project that has QtNature + * while there is no registered IQMakeEnvProvider that would provide any IQMakeEnv. + */ + private static class ConfigurationQMakeEnv implements IQMakeEnv { + + private static final String PRO_FILE_SUFFIX = ".pro"; //$NON-NLS-1$ + private static final String ENV_VAR_QMAKE = "QMAKE"; //$NON-NLS-1$ + + private final ICConfigurationDescription configuration; + + public ConfigurationQMakeEnv(ICConfigurationDescription configuration) { + this.configuration = configuration; + } + + @Override + public void destroy() { + } + + /** + * Returns QMakeEnvInfo resolved from a project in a generic way. + * If exists, the root-level .pro file is resolved as the one that is located directly under the project and has the same name. + * If exists, qmake executable is resolved from "QMAKE" environment variable that is stored in the project configuration. + * + * @return the QMakeEnvInfo instance if the project configuration exists; otherwise null. + */ + @Override + public QMakeEnvInfo getQMakeEnvInfo() { + if (configuration == null) { + return null; + } + IProject project = configuration.getProjectDescription().getProject(); + IFile proFile = project != null ? project.getFile(project.getName() + PRO_FILE_SUFFIX) : null; + + IEnvironmentVariable variable = CCorePlugin.getDefault().getBuildEnvironmentManager().getVariable(ENV_VAR_QMAKE, configuration, true); + String qmakeFilePath = variable != null ? variable.getValue() : null; + return new QMakeEnvInfo(proFile, qmakeFilePath, Collections.<String,String>emptyMap(), Collections.<IFile>emptyList()); + } + + } + +} diff --git a/qt/org.eclipse.cdt.qt.core/src/org/eclipse/cdt/internal/qt/core/index/QMakeInfo.java b/qt/org.eclipse.cdt.qt.core/src/org/eclipse/cdt/internal/qt/core/index/QMakeInfo.java new file mode 100644 index 00000000000..c4f769e7244 --- /dev/null +++ b/qt/org.eclipse.cdt.qt.core/src/org/eclipse/cdt/internal/qt/core/index/QMakeInfo.java @@ -0,0 +1,199 @@ +/* + * Copyright (c) 2013 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.index; + +import java.io.BufferedReader; +import java.io.File; +import java.io.IOException; +import java.io.InputStreamReader; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.regex.Pattern; + +import org.eclipse.cdt.qt.core.QtPlugin; +import org.eclipse.cdt.qt.core.index.IQtVersion; +import org.eclipse.cdt.qt.core.index.IQMakeInfo; +import org.eclipse.cdt.utils.spawner.ProcessFactory; + +/** + * Holder for QMake information. + */ +public final class QMakeInfo implements IQMakeInfo { + + // reg. exp. for parsing output of "qmake -query" command + public static final Pattern PATTERN_QUERY_LINE = Pattern.compile("^(\\w+):(.*)$"); + // reg. exp. for parsing output of "qmake -E file.pro" command (for QMake 3.0 only) + public static final Pattern PATTERN_EVAL_LINE = Pattern.compile("^([a-zA-Z0-9_\\.]+)\\s*=\\s*(.*)$"); + + /** + * Instance that is used to present an invalid IQMakeInfo. + */ + public static final IQMakeInfo INVALID = new QMakeInfo( + false, null, Collections.<String>emptyList(), Collections.<String>emptyList(), + Collections.<String>emptyList(), Collections.<String>emptyList(), Collections.<String>emptyList(), + Collections.<String>emptyList(), Collections.<String>emptyList(), Collections.<String>emptyList()); + + private final boolean valid; + private final IQtVersion qtVersion; + private final List<String> involvedQMakeFiles; + private final List<String> qtImportPath; + private final List<String> qtQmlPath; + private final List<String> includePath; + private final List<String> defines; + private final List<String> sourceFiles; + private final List<String> headerFiles; + private final List<String> otherFiles; + + private QMakeInfo(boolean valid, IQtVersion qtVersion, List<String> involvedQMakeFiles, List<String> qtImportPath, List<String> qtQmlPath, List<String> includePath, List<String> defines, List<String> sourceFiles, List<String> headerFiles, List<String> otherFiles) { + this.valid = valid; + this.qtVersion = qtVersion; + this.involvedQMakeFiles = Collections.unmodifiableList(involvedQMakeFiles); + this.qtImportPath = Collections.unmodifiableList(qtImportPath); + this.qtQmlPath = Collections.unmodifiableList(qtQmlPath); + this.includePath = Collections.unmodifiableList(includePath); + this.defines = Collections.unmodifiableList(defines); + this.sourceFiles = Collections.unmodifiableList(sourceFiles); + this.headerFiles = Collections.unmodifiableList(headerFiles); + this.otherFiles = Collections.unmodifiableList(otherFiles); + } + + public static IQMakeInfo create(File projectFile, File qmake, String[] extraEnv) { + return create(projectFile.getAbsolutePath(), qmake.getAbsolutePath(), extraEnv); + } + + public static IQMakeInfo create(String proPath, String qmakePath, String[] extraEnv) { + if (proPath == null || qmakePath == null) { + return INVALID; + } + + // run "qmake -query" + Map<String, String> qmake1 = exec(PATTERN_QUERY_LINE, extraEnv, qmakePath, "-query"); + if (qmake1 == null) + return INVALID; + + // check the qmake version + QMakeVersion version = QMakeVersion.create(qmake1.get(QMakeParser.KEY_QMAKE_VERSION)); + + // TODO - no support for pre-3.0 + // for QMake version 3.0 or newer, run "qmake -E file.pro" + Map<String, String> qmake2 = version != null && version.getMajor() >= 3 ? exec(PATTERN_EVAL_LINE, extraEnv, qmakePath, "-E", proPath) : Collections.<String,String>emptyMap(); + + IQtVersion qtVersion = QMakeVersion.create(qmake1.get(QMakeParser.KEY_QT_VERSION)); + List<String> qtImportPaths = QMakeParser.singleValue(qmake1, QMakeParser.KEY_QT_INSTALL_IMPORTS); + List<String> qtQmlPaths = QMakeParser.singleValue(qmake1, QMakeParser.KEY_QT_INSTALL_QML); + + List<String> involvedQMakeFiles = QMakeParser.qmake3DecodeValueList(qmake2, QMakeParser.KEY_QMAKE_INTERNAL_INCLUDED_FILES); + List<String> includePath = QMakeParser.qmake3DecodeValueList(qmake2, QMakeParser.KEY_INCLUDEPATH); + List<String> defines = QMakeParser.qmake3DecodeValueList(qmake2, QMakeParser.KEY_DEFINES); + List<String> sourceFiles = QMakeParser.qmake3DecodeValueList(qmake2, QMakeParser.KEY_SOURCES); + List<String> headerFiles = QMakeParser.qmake3DecodeValueList(qmake2, QMakeParser.KEY_HEADERS); + List<String> otherFiles = QMakeParser.qmake3DecodeValueList(qmake2, QMakeParser.KEY_HEADERS); + List<String> qmlImportPath = QMakeParser.qmake3DecodeValueList(qmake2, QMakeParser.KEY_QML_IMPORT_PATH); + + // combine qtImportPath and qtQmlPath from both qmake runs + List<String> realQtImportPaths = new ArrayList<String>(qtImportPaths); + realQtImportPaths.addAll(qmlImportPath); + List<String> realQtQmlPaths = new ArrayList<String>(qtQmlPaths); + realQtQmlPaths.addAll(qmlImportPath); + + return new QMakeInfo(true, qtVersion, involvedQMakeFiles, realQtImportPaths, realQtQmlPaths, includePath, defines, sourceFiles, headerFiles, otherFiles); + } + + @Override + public boolean isValid() { + return valid; + } + + @Override + public IQtVersion getQtVersion() { + return qtVersion; + } + + @Override + public List<String> getInvolvedQMakeFiles() { + return involvedQMakeFiles; + } + + @Override + public List<String> getQtImportPath() { + return qtImportPath; + } + + @Override + public List<String> getQtQmlPath() { + return qtQmlPath; + } + + @Override + public List<String> getIncludePath() { + return includePath; + } + + @Override + public List<String> getDefines() { + return defines; + } + + @Override + public List<String> getSourceFiles() { + return sourceFiles; + } + + @Override + public List<String> getHeaderFiles() { + return headerFiles; + } + + @Override + public List<String> getOtherFiles() { + return otherFiles; + } + + /** + * Executes a command and parses its output into a map. + * + * @param regex the reg. exp. used for parsing the output + * @param extraEnv the extra environment for command + * @param cmd the command line + * @return the map of resolved key-value pairs + */ + @SuppressWarnings("resource") + private static Map<String, String> exec(Pattern regex, String[] extraEnv, String...command) { + if (command.length < 1 || ! new File(command[0]).exists()) { + QtPlugin.log("qmake: cannot run command: " + (command.length > 0 ? command[0] : "")); + return null; + } + BufferedReader reader = null; + Process process = null; + try { + if (extraEnv != null && extraEnv.length > 0) { + process = ProcessFactory.getFactory().exec(command, extraEnv); + } else { + process = ProcessFactory.getFactory().exec(command); + } + reader = new BufferedReader(new InputStreamReader(process.getInputStream())); + return QMakeParser.parse(regex, reader); + } catch(IOException e) { + QtPlugin.log(e); + return null; + } finally { + if (reader != null) + try { + reader.close(); + } catch(IOException e) { + /* ignore */ + } + if (process != null) { + process.destroy(); + } + } + } + +} diff --git a/qt/org.eclipse.cdt.qt.core/src/org/eclipse/cdt/internal/qt/core/index/QMakeParser.java b/qt/org.eclipse.cdt.qt.core/src/org/eclipse/cdt/internal/qt/core/index/QMakeParser.java new file mode 100644 index 00000000000..c412ec976c6 --- /dev/null +++ b/qt/org.eclipse.cdt.qt.core/src/org/eclipse/cdt/internal/qt/core/index/QMakeParser.java @@ -0,0 +1,211 @@ +/* + * Copyright (c) 2013 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.index; + +import java.io.BufferedReader; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.eclipse.cdt.qt.core.QtPlugin; + +/** + * Provides a parser for QMake output. + */ +public final class QMakeParser { + + public static final String KEY_QMAKE_VERSION = "QMAKE_VERSION"; + public static final String KEY_QT_VERSION = "QT_VERSION"; + public static final String KEY_QT_INSTALL_IMPORTS = "QT_INSTALL_IMPORTS"; + public static final String KEY_QT_INSTALL_QML = "QT_INSTALL_QML"; + public static final String KEY_QMAKE_INTERNAL_INCLUDED_FILES = "QMAKE_INTERNAL_INCLUDED_FILES"; + public static final String KEY_SOURCES = "SOURCES"; + public static final String KEY_HEADERS = "HEADERS"; + public static final String KEY_INCLUDEPATH = "INCLUDEPATH"; + public static final String KEY_DEFINES = "DEFINES"; + public static final String KEY_QML_IMPORT_PATH = "QML_IMPORT_PATH"; + + /** + * Parses QMake output via a specified reg. exp. + * + * @param regex the reg. exp. + * @param reader the QMake output + * @return the map of parsed key-value pairs + * @throws IOException when io error happens + */ + public static Map<String, String> parse(Pattern regex, BufferedReader reader) throws IOException { + Map<String, String> result = new LinkedHashMap<String, String>(); + + String line; + while((line = reader.readLine()) != null) { + Matcher m = regex.matcher(line); + if (!m.matches() || m.groupCount() != 2) { + QtPlugin.log("qmake: cannot decode query line '" + line + '\''); + } else { + String key = m.group(1); + String value = m.group(2); + String oldValue = result.put(key, value); + if (oldValue != null) + QtPlugin.log("qmake: duplicate keys in query info '" + line + "' was '" + oldValue + '\''); + } + } + + return result; + } + + /** + * Returns a list with 0-1 values for a specific QMake variable. + * + * @param map the map + * @param key the QMake variable + * @return the list of values + */ + public static List<String> singleValue(Map<String, String> map, String key) { + String value = map.get(key); + return value == null ? Collections.<String>emptyList() : Collections.singletonList(value); + } + + /** + * Returns a list of values for a specific QMake variable that is decoded as a list of values. + * + * @param map the map + * @param key the QMake variable + * @return the list of values + */ + public static List<String> qmake3DecodeValueList(Map<String, String> map, String key) { + String value = map.get(key); + if (value == null) { + return Collections.emptyList(); + } + + List<String> result = new ArrayList<String>(); + for (String item : qmake3SplitValueList(value)) { + result.add(qmake3DecodeValue(item)); + } + return result; + } + + /** + * Decodes a specified QMake variable value. + * + * @param value the value + * @return the decoded value + */ + public static String qmake3DecodeValue(String value) { + int length = value.length(); + if (length >= 2 && value.charAt(0) == '"' && value.charAt(length - 1) == '"') { + value = value.substring(1, length - 1); + length = value.length(); + } + StringBuilder sb = new StringBuilder(length); + for (int i = 0; i < length; i ++) { + char c = value.charAt(i); + if (c == '\\') { + ++ i; + if (i < length) { + char next = value.charAt(i); + switch (next) { + case 'r': + sb.append('\r'); + break; + case 'n': + sb.append ('\n'); + break; + case 't': + sb.append ('\t'); + break; + case '\\': + case '\'': + case '"': + sb.append (next); + break; + case 'x': + i += 2; + if (i < length) { + char first = value.charAt(i - 1); + char second = value.charAt(i); + if (first >= '0' && first <= '9' && second >= '0' && second <= '9') { + sb.append ((char) ((first - '0') * 16 + (second - '0'))); + } + } + } + } + } else { + sb.append (c); + } + } + return sb.toString(); + } + + /** + * Splits a specified QMake variable value into a list of values. + * + * @param value the value + * @return the list of values + */ + private static List<String> qmake3SplitValueList(String value) { + List<String> result = new ArrayList<String>(); + StringBuilder sb = new StringBuilder(); + char quote = 0; + boolean hadWord = false; + final int length = value.length(); + for (int i = 0; i < length; i ++) { + char c = value.charAt(i); + if (quote == c) { + quote = 0; + hadWord = true; + sb.append(c); + continue; + } + + switch (c) { + case '"': + case '\'': + quote = c; + hadWord = true; + break; + case ' ': + case '\t': + if (quote == 0) { + if (hadWord) { + result.add(sb.toString()); + sb.delete(0, sb.length()); + hadWord = false; + } + continue; + } + break; + case '\\': + if (i + 1 < length) { + char nextChar = value.charAt(i + 1); + if (nextChar == '\'' || nextChar == '"' || nextChar == '\\') { + sb.append(c); + c = nextChar; + ++ i; + } + } + //$FALL-THROUGH$ + default: + hadWord = true; + break; + } + sb.append (c); + } + + if (hadWord) { + result.add(sb.toString()); + } + + return Collections.unmodifiableList(result); + } +} diff --git a/qt/org.eclipse.cdt.qt.core/src/org/eclipse/cdt/internal/qt/core/index/QMakeProjectInfo.java b/qt/org.eclipse.cdt.qt.core/src/org/eclipse/cdt/internal/qt/core/index/QMakeProjectInfo.java new file mode 100644 index 00000000000..331e662235e --- /dev/null +++ b/qt/org.eclipse.cdt.qt.core/src/org/eclipse/cdt/internal/qt/core/index/QMakeProjectInfo.java @@ -0,0 +1,426 @@ +/* + * Copyright (c) 2013 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.index; + +import java.io.File; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.CopyOnWriteArrayList; + +import org.eclipse.cdt.core.model.CoreModel; +import org.eclipse.cdt.core.settings.model.CProjectDescriptionEvent; +import org.eclipse.cdt.core.settings.model.ICConfigurationDescription; +import org.eclipse.cdt.core.settings.model.ICDescriptionDelta; +import org.eclipse.cdt.core.settings.model.ICProjectDescription; +import org.eclipse.cdt.core.settings.model.ICProjectDescriptionListener; +import org.eclipse.cdt.qt.core.QtNature; +import org.eclipse.cdt.qt.core.index.IQMakeEnv; +import org.eclipse.cdt.qt.core.index.IQMakeEnvProvider; +import org.eclipse.cdt.qt.core.index.IQMakeProjectInfo; +import org.eclipse.cdt.qt.core.index.IQMakeProjectInfoListener; +import org.eclipse.cdt.qt.core.index.QMakeEnvInfo; +import org.eclipse.cdt.qt.core.index.IQMakeInfo; +import org.eclipse.core.resources.IFile; +import org.eclipse.core.resources.IProject; +import org.eclipse.core.resources.IResource; +import org.eclipse.core.resources.IResourceChangeEvent; +import org.eclipse.core.resources.IResourceChangeListener; +import org.eclipse.core.resources.IResourceDelta; +import org.eclipse.core.resources.IResourceDeltaVisitor; +import org.eclipse.core.resources.IWorkspaceRoot; +import org.eclipse.core.resources.ResourcesPlugin; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IPath; +import org.eclipse.core.runtime.Path; + +/** + * Represents a QMake project information that is based on an activate project configuration of a specified related IProject. + * Allows to resolve actual information and listen its change. + * Manages life-cycle of all QMakeProjectInfo instances. + */ +public final class QMakeProjectInfo implements IQMakeProjectInfo, ICProjectDescriptionListener { + + private static final RCListener LISTENER = new RCListener(); + // sync object for CACHE field + private static final Object SYNC = new Object(); + // a map of all QMakeProjectInfo instances + private static final Map<IProject,QMakeProjectInfo> CACHE = new HashMap<IProject,QMakeProjectInfo>(); + + // called by QtPlugin activator to setup this class + public static final void start() { + ResourcesPlugin.getWorkspace().addResourceChangeListener(LISTENER, IResourceChangeEvent.POST_CHANGE | IResourceChangeEvent.PRE_CLOSE | IResourceChangeEvent.PRE_DELETE); + } + + // called by QtPlugin activator to clean up this class + public static final void stop() { + ResourcesPlugin.getWorkspace().removeResourceChangeListener(LISTENER); + synchronized (SYNC) { + while (true) { + Iterator<IProject> iterator = CACHE.keySet().iterator(); + if (!iterator.hasNext()) { + break; + } + removeProjectFromCache(iterator.next()); + } + } + } + + /** + * Returns a QMakeProjectInfo for an active project configuration of a specified project. + * + * @param project the project + * @return the QMakeProjectInfo; or null if the project does not have QtNature + */ + public static QMakeProjectInfo getQMakeProjectInfoFor(IProject project) { + synchronized (SYNC) { + QMakeProjectInfo info = CACHE.get(project); + if (info == null) { + if (QtNature.hasNature(project)) { + info = new QMakeProjectInfo(project); + CACHE.put(project,info); + } + } + return info; + } + } + + // removes the project from the CACHE + private static void removeProjectFromCache(IResource project) { + QMakeProjectInfo info; + synchronized (SYNC) { + info = CACHE.remove(project); + } + if (info != null) { + info.destroy(); + } + } + + private final ICProjectDescription projectDescription; + + // sync object for all mutable fields + private final Object sync = new Object(); + // true if this instance still registered in CACHE + private boolean live = true; + // a set of sensitive files that might affects actual QMake information + private final SensitiveSet sensitiveFilePathSet = new SensitiveSet(); + // an active project configuration + private ControllerImpl activeController; + // the last calculated QMake info; null if not calculated + private IQMakeInfo qmakeInfo = null; + // listeners + private final List<IQMakeProjectInfoListener> listeners = new CopyOnWriteArrayList<IQMakeProjectInfoListener>(); + + private QMakeProjectInfo(IProject project) { + projectDescription = CoreModel.getDefault().getProjectDescriptionManager().getProjectDescription(project); + CoreModel.getDefault().addCProjectDescriptionListener(this, ICDescriptionDelta.ACTIVE_CFG); + updateActiveConfiguration(); + } + + // called from removeProjectFromCache only + private void destroy() { + synchronized (sync) { + if (! live) { + return; + } + live = false; + CoreModel.getDefault().removeCProjectDescriptionListener(this); + removeActiveConfiguration(); + qmakeInfo = QMakeInfo.INVALID; + } + } + + // removes active configuration + private void removeActiveConfiguration() { + ControllerImpl previous = activeController; + activeController = null; + if (previous != null) { + previous.destroy(); + } + } + + // updates active configuration + private void updateActiveConfiguration() { + ICConfigurationDescription configuration = projectDescription.getActiveConfiguration(); + if (configuration != null) { + activeController = new ControllerImpl(configuration); + } + scheduleFetchQMakeInfo(); + } + + // called on active project configuration change + @Override + public void handleEvent(CProjectDescriptionEvent event) { + synchronized (sync) { + removeActiveConfiguration(); + updateActiveConfiguration(); + } + } + + @Override + public void addListener(IQMakeProjectInfoListener listener) { + listeners.add(listener); + } + + @Override + public void removeListener(IQMakeProjectInfoListener listener) { + listeners.remove(listener); + } + + // calculates (if does not exist) and returns actual QMake info + @Override + public IQMakeInfo getActualInfo() { + synchronized (sync) { + if (! live) { + return QMakeInfo.INVALID; + } + if (qmakeInfo == null) { + fetchQMakeInfo(); + } + return qmakeInfo; + } + } + + // calculates actual QMake info + private void fetchQMakeInfo() { + // retrieves IQMakeEnvInfo from IQMakeEnv + IQMakeEnv qmakeEnv = activeController != null ? activeController.getQMakeEnv() : null; + QMakeEnvInfo qmakeEnvInfo = qmakeEnv != null ? qmakeEnv.getQMakeEnvInfo() : null; + + // retrieves .pro file path + String proFilePath = toFilePath(qmakeEnvInfo != null ? qmakeEnvInfo.getProFile() : null); + // retrieves qmake executable path + String qmakeFilePath = qmakeEnvInfo != null ? qmakeEnvInfo.getQMakeFilePath() : null; + // retries environment + List<String> envList = new ArrayList<String>(); + Map<String, String> envMap = qmakeEnvInfo != null ? qmakeEnvInfo.getEnvironment() : Collections.<String,String>emptyMap(); + for (Map.Entry<String,String> entry : envMap.entrySet()) { + envList.add(entry.getKey() + "=" + entry.getValue()); + } + + // calculates actual QMake info + qmakeInfo = QMakeInfo.create(proFilePath, qmakeFilePath, envList.toArray(new String[envList.size()])); + + // calculates a new set of sensitive file paths + sensitiveFilePathSet.clear(); + Set<IFile> envSensFiles = qmakeEnvInfo != null ? qmakeEnvInfo.getSensitiveFiles() : Collections.<IFile>emptySet(); + for (IFile sensitiveFile : envSensFiles) { + if (sensitiveFile != null) { + sensitiveFilePathSet.addSensitiveFile(sensitiveFile); + } + } + if (proFilePath != null) { + sensitiveFilePathSet.addSensitiveFile(proFilePath); + } + List<String> sensitiveFiles = qmakeInfo.getInvolvedQMakeFiles(); + if (sensitiveFiles != null) { + for (String sensitiveFile : sensitiveFiles) { + sensitiveFilePathSet.addSensitiveFile(sensitiveFile); + } + } + } + + // converts IFile to absolute path + private static String toFilePath(IFile file) { + if (file != null) { + IPath rawLocation = file.getRawLocation(); + if (rawLocation != null) { + rawLocation = rawLocation.makeAbsolute(); + if (rawLocation != null) { + File f = rawLocation.toFile(); + if (f != null) { + return f.getAbsolutePath(); + } + } + } + } + return null; + } + + // checks if any of the specified files is a sensitive file + private boolean containsAnySensitiveFile(Set<IPath> files) { + synchronized (sync) { + if (live) { + for (Iterator<IPath> iterator = files.iterator(); iterator.hasNext();) { + IPath path = iterator.next(); + if (sensitiveFilePathSet.contains(path)) { + return true; + } + } + } + return false; + } + } + + // resets actual QMake info and notifies all listeners that QMake information has changes + private void scheduleFetchQMakeInfo() { + synchronized (sync) { + if (! live) { + return; + } + qmakeInfo = null; + } + for (IQMakeProjectInfoListener listener : listeners) { + listener.qmakeInfoChanged(); + } + } + + /** + * Represents a project configuration. + */ + private final class ControllerImpl implements IQMakeEnvProvider.IController { + + private final ICConfigurationDescription configuration; + private final IQMakeEnv qmakeEnv; + + public ControllerImpl(ICConfigurationDescription configuration) { + this.configuration = configuration; + // qmakeEnv created from registry of qmakeEnvProvider extensions + this.qmakeEnv = QMakeEnvProviderManager.getInstance().createEnv(this); + } + + public void destroy() { + qmakeEnv.destroy(); + } + + public IQMakeEnv getQMakeEnv() { + return qmakeEnv; + } + + @Override + public ICConfigurationDescription getConfiguration() { + return configuration; + } + + @Override + public void scheduleUpdate() { + scheduleFetchQMakeInfo(); + } + + } + + /** + * Listens on Eclipse file system changes. + */ + private static final class RCListener implements IResourceChangeListener { + + @Override + public void resourceChanged(IResourceChangeEvent event) { + RDVisitor visitor = new RDVisitor(); + + // collect project to delete and changed files + switch (event.getType()) { + case IResourceChangeEvent.PRE_CLOSE: + case IResourceChangeEvent.PRE_DELETE: + IResource project = event.getResource(); + if (project != null && project.getType() == IResource.PROJECT) { + visitor.addProjectToDelete(project); + } + break; + case IResourceChangeEvent.POST_CHANGE: + IResourceDelta delta = event.getDelta(); + if (delta != null) { + try { + delta.accept(visitor); + } catch (CoreException e) { + // empty + } + } + break; + } + + // process collected data + visitor.process(); + } + + } + + private static final class RDVisitor implements IResourceDeltaVisitor { + + private final Set<IResource> projectsToDelete = new HashSet<IResource>(); + private final Set<IPath> changedFiles = new HashSet<IPath>(); + + @Override + public boolean visit(IResourceDelta delta) throws CoreException { + IResource resource = delta.getResource(); + if (resource != null) { + switch (resource.getType()) { + case IResource.FILE: + addChangedFile(resource); + return false; + case IResource.PROJECT: + if (delta.getKind() == IResourceDelta.REMOVED) { + addProjectToDelete(resource); + return false; + } + break; + } + } + return true; + } + + private void addProjectToDelete(IResource project) { + projectsToDelete.add(project); + } + + private void addChangedFile(IResource file) { + IPath fullPath = file.getFullPath(); + if (fullPath != null) { + changedFiles.add(fullPath); + } + } + + public void process() { + // removing projects from CACHE + for(IResource project : projectsToDelete) { + removeProjectFromCache(project); + } + + List<QMakeProjectInfo> infos; + synchronized (SYNC) { + infos = new ArrayList<QMakeProjectInfo>(CACHE.values()); + } + for (QMakeProjectInfo info : infos) { + // checking if any of the changed files affects QMakeProjectInfo + if (info.containsAnySensitiveFile(changedFiles)) { + // if so then scheduling update + info.scheduleFetchQMakeInfo(); + } + } + } + + } + + private static final class SensitiveSet extends HashSet<IPath> { + + // adds a sensitive file in form of a specified absolute path + private void addSensitiveFile(String sensitiveFile) { + IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot(); + IFile[] files = root.findFilesForLocation(Path.fromOSString(sensitiveFile)); + if (files != null && files.length > 0) { + IFile file = files[0]; + addSensitiveFile(file); + } + } + + // adds a sensitive file in form of a IFile + private void addSensitiveFile(IFile file) { + IPath fullPath = file.getFullPath(); + if (fullPath != null) { + add(fullPath); + } + } + + } + +} diff --git a/qt/org.eclipse.cdt.qt.core/src/org/eclipse/cdt/internal/qt/core/index/QMakeVersion.java b/qt/org.eclipse.cdt.qt.core/src/org/eclipse/cdt/internal/qt/core/index/QMakeVersion.java new file mode 100644 index 00000000000..b3052bea094 --- /dev/null +++ b/qt/org.eclipse.cdt.qt.core/src/org/eclipse/cdt/internal/qt/core/index/QMakeVersion.java @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2013 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.index; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.eclipse.cdt.qt.core.QtPlugin; +import org.eclipse.cdt.qt.core.index.IQtVersion; + +/** + * A container class to interpret and store value of the the qmake version. + */ +public final class QMakeVersion implements IQtVersion { + + // QMAKE_VERSION looks like 2.01a or 3.0 + private static final Pattern REGEXP = Pattern.compile( "([\\d]+)\\.([\\d]+).*" ); + + // parses major and minor version numbers only + public static QMakeVersion create(String version) { + if (version == null) { + return null; + } + + Matcher m = REGEXP.matcher(version.trim()); + if (!m.matches()) { + return null; + } + + try { + int major = Integer.parseInt(m.group(1)); + int minor = Integer.parseInt(m.group(2)); + return new QMakeVersion(major, minor); + } catch(NumberFormatException e) { + QtPlugin.log(e); + } + return null; + } + + private final int major; + private final int minor; + + private QMakeVersion(int major, int minor) { + this.major = major; + this.minor = minor; + } + + @Override + public int getMajor() { + return major; + } + + @Override + public int getMinor() { + return minor; + } + +} diff --git a/qt/org.eclipse.cdt.qt.core/src/org/eclipse/cdt/qt/core/QtNature.java b/qt/org.eclipse.cdt.qt.core/src/org/eclipse/cdt/qt/core/QtNature.java index f58e2cd190e..69c2df2665e 100644 --- a/qt/org.eclipse.cdt.qt.core/src/org/eclipse/cdt/qt/core/QtNature.java +++ b/qt/org.eclipse.cdt.qt.core/src/org/eclipse/cdt/qt/core/QtNature.java @@ -16,7 +16,16 @@ import org.eclipse.core.runtime.CoreException; public class QtNature implements IProjectNature { public static final String ID = "org.eclipse.cdt.qt.core.qtNature"; - + + public static boolean hasNature(IProject project) { + try { + return project.hasNature(ID); + } catch (CoreException e) { + QtPlugin.log(e); + return false; + } + } + private IProject project; @Override @@ -36,5 +45,5 @@ public class QtNature implements IProjectNature { public void setProject(IProject project) { this.project = project; } - + } diff --git a/qt/org.eclipse.cdt.qt.core/src/org/eclipse/cdt/qt/core/QtPlugin.java b/qt/org.eclipse.cdt.qt.core/src/org/eclipse/cdt/qt/core/QtPlugin.java index 825b5614cc7..f11e1b22b9e 100644 --- a/qt/org.eclipse.cdt.qt.core/src/org/eclipse/cdt/qt/core/QtPlugin.java +++ b/qt/org.eclipse.cdt.qt.core/src/org/eclipse/cdt/qt/core/QtPlugin.java @@ -1,9 +1,13 @@ package org.eclipse.cdt.qt.core; -import org.osgi.framework.BundleActivator; +import org.eclipse.cdt.core.model.CModelException; +import org.eclipse.cdt.internal.qt.core.index.QMakeProjectInfo; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.Plugin; +import org.eclipse.core.runtime.Status; import org.osgi.framework.BundleContext; -public class QtPlugin implements BundleActivator { +public class QtPlugin extends Plugin { public static final String ID = "org.eclipse.cdt.qt.core"; public static final String SIGNAL_SLOT_TAGGER_ID = ID + ".signalslot.tagger"; @@ -11,19 +15,29 @@ public class QtPlugin implements BundleActivator { public static final int SignalSlot_Mask_signal = 1; public static final int SignalSlot_Mask_slot = 2; + public static final String QMAKE_ENV_PROVIDER_EXT_POINT_NAME = "qmakeEnvProvider"; //$NON-NLS-1$ + public static final String QMAKE_ENV_PROVIDER_ID = ID + "." + QMAKE_ENV_PROVIDER_EXT_POINT_NAME; //$NON-NLS-1$ + + private static QtPlugin INSTANCE; private static BundleContext context; static BundleContext getContext() { return context; } + static QtPlugin getDefault() { + return INSTANCE; + } + /* * (non-Javadoc) * @see org.osgi.framework.BundleActivator#start(org.osgi.framework.BundleContext) */ @Override public void start(BundleContext bundleContext) throws Exception { + INSTANCE = this; QtPlugin.context = bundleContext; + QMakeProjectInfo.start(); } /* @@ -32,7 +46,34 @@ public class QtPlugin implements BundleActivator { */ @Override public void stop(BundleContext bundleContext) throws Exception { + QMakeProjectInfo.stop(); QtPlugin.context = null; + INSTANCE = null; + } + + public static void log(String e) { + log(IStatus.INFO, e, null); + } + + public static void log(Throwable e) { + String msg= e.getMessage(); + if (msg == null) { + log("Error", e); //$NON-NLS-1$ + } else { + log("Error: " + msg, e); //$NON-NLS-1$ + } + } + + public static void log(String message, Throwable e) { + Throwable nestedException; + if (e instanceof CModelException && (nestedException = ((CModelException)e).getException()) != null) { + e = nestedException; + } + log(IStatus.ERROR, message, e); + } + + public static void log(int code, String msg, Throwable e) { + getDefault().getLog().log(new Status(code, ID, msg, e)); } } diff --git a/qt/org.eclipse.cdt.qt.core/src/org/eclipse/cdt/qt/core/index/IQMakeEnv.java b/qt/org.eclipse.cdt.qt.core/src/org/eclipse/cdt/qt/core/index/IQMakeEnv.java new file mode 100644 index 00000000000..574d3fb8c6c --- /dev/null +++ b/qt/org.eclipse.cdt.qt.core/src/org/eclipse/cdt/qt/core/index/IQMakeEnv.java @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2013 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.qt.core.index; + +/** + * Represents a QMake environment. It is usually created by IQMakeEnvProvider.createEnv() method for a specific project configuration. + */ +public interface IQMakeEnv { + + /** + * Notifies that this environment is no longer used. + */ + void destroy(); + + /** + * Returns an actual QMake environment information that is used for a single qmake run to retrieve QMake information. + * + * @return the actual QMake environment information + */ + QMakeEnvInfo getQMakeEnvInfo(); + +} diff --git a/qt/org.eclipse.cdt.qt.core/src/org/eclipse/cdt/qt/core/index/IQMakeEnvProvider.java b/qt/org.eclipse.cdt.qt.core/src/org/eclipse/cdt/qt/core/index/IQMakeEnvProvider.java new file mode 100644 index 00000000000..2dd5053240e --- /dev/null +++ b/qt/org.eclipse.cdt.qt.core/src/org/eclipse/cdt/qt/core/index/IQMakeEnvProvider.java @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2013 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.qt.core.index; + +import org.eclipse.cdt.core.settings.model.ICConfigurationDescription; + +/** + * Represents a provider for IQMakeEnv which is used to specify an environment for qmake run. + * This class needs to be registered via org.eclipse.cdt.qt.core.qmakeEnvProvider extension point. + */ +public interface IQMakeEnvProvider { + + /** + * Creates a QMake environment for a specific IController (aka a project configuration). + * + * @param controller the controller + * @return the IQMakeEnv instance that is used for qmake run; + * or null if this provider cannot create IQMakeEnv instance for the specified IController. + */ + IQMakeEnv createEnv(IController controller); + + /** + * Represents a project configuration and provides a control over the environment. + * + * This class is not meant to be implemented. + */ + public interface IController { + + /** + * Returns a project configuration for which a QMake environment should be supplied. + * + * @return the project configuration + */ + ICConfigurationDescription getConfiguration(); + + /** + * Request the controller to schedule a new qmake run to retrieve new QMake information. + * This method should be called when there is any change in IQMakeEnv that might affect resulting IQMakeEnvInfo. + */ + void scheduleUpdate(); + + } + +} diff --git a/qt/org.eclipse.cdt.qt.core/src/org/eclipse/cdt/qt/core/index/IQMakeInfo.java b/qt/org.eclipse.cdt.qt.core/src/org/eclipse/cdt/qt/core/index/IQMakeInfo.java new file mode 100644 index 00000000000..2b8cd5d3c9e --- /dev/null +++ b/qt/org.eclipse.cdt.qt.core/src/org/eclipse/cdt/qt/core/index/IQMakeInfo.java @@ -0,0 +1,92 @@ +/* + * Copyright (c) 2013 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.qt.core.index; + +import java.util.List; + +/** + * Represents a QMake information. + * + * Note that current implementation of does not handle support QMake 2.0 in full range so provided information might be incomplete. + * + * This class is not meant to be implemented. + */ +public interface IQMakeInfo { + + /** + * Returns whether this information is valid. If invalid, then returned value of the other method is unspecified. + * + * @return true if this information is valid + */ + boolean isValid(); + + /** + * Returns a Qt version as provided by "qmake -query" command. + * + * @return the Qt version + */ + IQtVersion getQtVersion(); + + /** + * Returns a list of QMake files (.pro, .pri, .prf, ...) that are involved in resolving this information as retrieved + * via "qmake -E file.pro" command. + * + * @return the list of involved QMake files + */ + List<String> getInvolvedQMakeFiles(); + + /** + * Returns a list of Qt Import paths. Represents QT_IMPORT_PATH that is used by QDeclarativeEngine (aka QtQuick1) runtime to load QML modules. + * + * @return the list of Qt Import paths + */ + List<String> getQtImportPath(); + + /** + * Returns a list of Qt Qml paths that is used by QQmlEngine (aka QtQuick2) runtime to load QML modules. + * + * @return the list of Qt Qml paths + */ + List<String> getQtQmlPath(); + + /** + * Returns a list of include paths that are used for compilation of a related project. + * + * @return the list of include paths + */ + List<String> getIncludePath(); + + /** + * Returns a list of defines that are used for compilation of a related project i.e. specified via DEFINES QMake variable. + * + * @return the list of defines. + */ + List<String> getDefines(); + + /** + * Returns a list of source file paths i.e. specified via SOURCES QMake variable. + * + * @return the list of source file paths + */ + List<String> getSourceFiles(); + + /** + * Returns a list of header file paths i.e. specified via HEADERS QMake variable. + * + * @return the list of header file paths + */ + List<String> getHeaderFiles(); + + /** + * Returns a list of other file paths i.e. specified via OTHER_FILES QMake variable. + * + * @return the list of other file paths + */ + List<String> getOtherFiles(); + +} diff --git a/qt/org.eclipse.cdt.qt.core/src/org/eclipse/cdt/qt/core/index/IQMakeProjectInfo.java b/qt/org.eclipse.cdt.qt.core/src/org/eclipse/cdt/qt/core/index/IQMakeProjectInfo.java new file mode 100644 index 00000000000..60edc762bb8 --- /dev/null +++ b/qt/org.eclipse.cdt.qt.core/src/org/eclipse/cdt/qt/core/index/IQMakeProjectInfo.java @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2013 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.qt.core.index; + +/** + * Represents provider of QMake information. + * + * This class is not meant to be implemented. + */ +public interface IQMakeProjectInfo { + + /** + * Add a listener that notifies about possible changes of IQMakeInfo retrieved from getActualInfo() method. + * + * @param listener the listener + */ + void addListener(IQMakeProjectInfoListener listener); + + /** + * Removes a listener that was added via addListener() method. + * + * @param listener the listener + */ + void removeListener(IQMakeProjectInfoListener listener); + + /** + * Returns an actual QMake information. + * + * Note that this is a long-term operation and the method call is blocked until an actual QMake information is calculated. + * + * @return non-null IQMakeInfo instance representing the actual QMake information + */ + IQMakeInfo getActualInfo(); + +} diff --git a/qt/org.eclipse.cdt.qt.core/src/org/eclipse/cdt/qt/core/index/IQMakeProjectInfoListener.java b/qt/org.eclipse.cdt.qt.core/src/org/eclipse/cdt/qt/core/index/IQMakeProjectInfoListener.java new file mode 100644 index 00000000000..ba132f02266 --- /dev/null +++ b/qt/org.eclipse.cdt.qt.core/src/org/eclipse/cdt/qt/core/index/IQMakeProjectInfoListener.java @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2013 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.qt.core.index; + +/** + * A listener used for notifying that a QMake information provided by IQMakeProjectInfo might have changed. + */ +public interface IQMakeProjectInfoListener { + + /** + * Notifies that a QMake information provided by IQMakeProjectInfo might have changed. + * A new QMake information can be read via IQMakeProjectInfo.getQMakeInfo() method. + */ + void qmakeInfoChanged(); + +} diff --git a/qt/org.eclipse.cdt.qt.core/src/org/eclipse/cdt/qt/core/index/IQtVersion.java b/qt/org.eclipse.cdt.qt.core/src/org/eclipse/cdt/qt/core/index/IQtVersion.java new file mode 100644 index 00000000000..5acdc9373d1 --- /dev/null +++ b/qt/org.eclipse.cdt.qt.core/src/org/eclipse/cdt/qt/core/index/IQtVersion.java @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2013 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.qt.core.index; + +/** + * Represents a Qt version in form of major and minor number. + */ +public interface IQtVersion { + + /** + * Returns major version number. + * @return the major version number + */ + int getMajor(); + + /** + * Returns minor version number. + * @return the minor version number + */ + int getMinor(); + +} diff --git a/qt/org.eclipse.cdt.qt.core/src/org/eclipse/cdt/qt/core/index/QMakeEnvInfo.java b/qt/org.eclipse.cdt.qt.core/src/org/eclipse/cdt/qt/core/index/QMakeEnvInfo.java new file mode 100644 index 00000000000..cd7dee6ff58 --- /dev/null +++ b/qt/org.eclipse.cdt.qt.core/src/org/eclipse/cdt/qt/core/index/QMakeEnvInfo.java @@ -0,0 +1,81 @@ +/* + * Copyright (c) 2013 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.qt.core.index; + +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +import org.eclipse.core.resources.IFile; + +/** + * Holds data describing QMake environment (pro file, qmake file, env. vars.) for a specific QMake run provided by QMakeEnv instance. + */ +public final class QMakeEnvInfo { + + private final IFile proFile; + private final String qmakeFilePath; + private final Map<String, String> environment; + private final Set<IFile> sensitiveFiles; + + /** + * Creates QMakeEnvInfo. + * + * @param proFile the root-level .pro file + * @param qmakeFilePath the absolute path of qmake executable + * @param environment environment variables + * @param sensitiveFiles the list of IFile that needs to be tracked by IQMakeProjectInfo since their change might affect + * an environment for running qmake. + */ + public QMakeEnvInfo(IFile proFile, String qmakeFilePath, Map<String,String> environment, Collection<IFile> sensitiveFiles) { + this.proFile = proFile; + this.qmakeFilePath = qmakeFilePath; + this.environment = environment != null ? new HashMap<String,String>(environment) : Collections.<String,String>emptyMap(); + this.sensitiveFiles = sensitiveFiles != null ? new HashSet<IFile>(sensitiveFiles) : Collections.<IFile>emptySet(); + } + + /** + * Returns IFile of .pro file. + * + * @return the .pro file + */ + public IFile getProFile() { + return proFile; + } + + /** + * Returns an absolute path of qmake executable. + * + * @return the qmake path + */ + public String getQMakeFilePath() { + return qmakeFilePath; + } + + /** + * Returns a map of environment variables that are used for qmake run. + * + * @return the environment + */ + public Map<String, String> getEnvironment() { + return environment; + } + + /** + * Returns a list of IFile that might affect environment of qmake run. + * + * @return the list sensitive files + */ + public Set<IFile> getSensitiveFiles() { + return sensitiveFiles; + } + +} diff --git a/qt/org.eclipse.cdt.qt.core/src/org/eclipse/cdt/qt/core/index/QMakeProjectInfoFactory.java b/qt/org.eclipse.cdt.qt.core/src/org/eclipse/cdt/qt/core/index/QMakeProjectInfoFactory.java new file mode 100644 index 00000000000..758b7135ff3 --- /dev/null +++ b/qt/org.eclipse.cdt.qt.core/src/org/eclipse/cdt/qt/core/index/QMakeProjectInfoFactory.java @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2013 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.qt.core.index; + +import org.eclipse.cdt.internal.qt.core.index.QMakeProjectInfo; +import org.eclipse.core.resources.IProject; + +/** + * A factory for QMakeProjectInfo instances. + */ +public final class QMakeProjectInfoFactory { + + private QMakeProjectInfoFactory() { + } + + /** + * Provides a IQMakeProjectInfo for an active project configuration + * in a specified project. + * + * @param project the project + * @return IQMakeProjectInfo representing an activate project configuration + * in the specified project. + */ + public static IQMakeProjectInfo getForActiveConfigurationIn(IProject project) { + return QMakeProjectInfo.getQMakeProjectInfoFor(project); + } + +} |