Skip to main content
summaryrefslogtreecommitdiffstats
blob: aba50fbcc08a81b612f7f2896d0a7837ff492fb9 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
/*
 * 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;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileFilter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import org.eclipse.cdt.core.language.settings.providers.LanguageSettingsSerializableProvider;
import org.eclipse.cdt.core.settings.model.CIncludePathEntry;
import org.eclipse.cdt.core.settings.model.ICConfigurationDescription;
import org.eclipse.cdt.core.settings.model.ICLanguageSettingEntry;
import org.eclipse.cdt.core.settings.model.ICSettingEntry;
import org.eclipse.cdt.utils.spawner.ProcessFactory;
import org.eclipse.core.resources.IResource;
import org.w3c.dom.Element;
import org.w3c.dom.Node;

/**
 * Discovers and persists the list of Qt include paths for a particular installation of
 * Qt.  The Qt installation is described by the path to qmake.
 * <p>
 * Qt uses a tool called qmake to generate makefiles for Qt projects.  The tool has a
 * query mode that can be used to discover information about the Qt installation.  Here
 * qmake is used to build a list of all installed Qt include paths.
 * <p>
 * These paths are persisted into a file called language-settings.xml in the workspace
 * metadata area.
 *
 * @see QtIncludePathsProvider
 */
public class QtIncludePaths extends LanguageSettingsSerializableProvider {

	/**
	 * The path to the qmake executable uniquely identifies this installation.
	 */
	private final String qmakePath;

	/**
	 * The cached data is reloaded when the qmake executable is modified.
	 */
	private long qmakeModTime;

	/**
	 * The cached data is reloaded when the folder holding the include paths
	 * is removed.
	 */
	private String qtInstallHeadersPath;

	/**
	 * The cached data is reloaded when the folder containing the include folders is
	 * modified.
	 */
	private long qtInstallHeadersModTime;

	private static final String ATTR_QMAKE = "qmake";
	private static final String ATTR_QMAKE_MOD = "qmakeModification";
	private static final String ATTR_QT_INSTALL_HEADERS = "QT_INSTALL_HEADERS";
	private static final String ATTR_QT_INSTALL_HEADERS_MOD = "qtInstallHeadersModification";

	/**
	 * Create a new instance of the include path wrapper for the Qt installation for
	 * the given qmake binary.
	 */
	public QtIncludePaths(String qmakePath) {
		this.qmakePath = qmakePath;
	}

	/**
	 * Create and load an instance of QtIncludePaths from data that was serialized into the
	 * given XML element.  Return null if an instance cannot be loaded or if the installation
	 * is no longer valid.
	 */
	public static QtIncludePaths loadFrom(Node node) {
		if (node.getNodeType() != Node.ELEMENT_NODE)
			return null;

		Element element = (Element) node;
		String qmakePath = element.getAttribute(ATTR_QMAKE);
		if (qmakePath == null
		 || qmakePath.isEmpty())
			return null;

		QtIncludePaths qtIncludePaths = new QtIncludePaths(qmakePath);
		qtIncludePaths.load(element);
		return qtIncludePaths;
	}

	public String getQMakePath() {
		return qmakePath;
	}

	/**
	 * Return true if the receiver points to a valid Qt installation and false otherwise.
	 * The installation is considered valid if an executable qmake binary exists at the
	 * expected location.
	 */
	public boolean isValid() {
		if (qmakePath == null
		 || qmakePath.isEmpty())
			return false;

		File qmake = new File(qmakePath);
		return qmake.exists()
			&& qmake.canExecute();
	}

	@Override
	public boolean equals(Object obj) {
		if (!(obj instanceof QtIncludePaths))
			return super.equals(obj);

		// Include paths are equivalent when they point to the same qmake binary.  All other
		// values are reloaded from that binary and do not need to be directly compared.
		QtIncludePaths other = (QtIncludePaths) obj;
		return qmakePath == null ? other.qmakePath == null : qmakePath.equals(other.qmakePath);
	}

	@Override
	public int hashCode() {
		return qmakePath == null ? 0 : qmakePath.hashCode();
	}

	/**
	 * Return a current list of the include paths for this Qt installation.  Return null if
	 * no such paths can be found.
	 * <p>
	 * Updates the cached results if needed.  If the settings are updated then the new list
	 * will be serialized into the workspace metadata area.
	 */
	@Override
	public List<ICLanguageSettingEntry> getSettingEntries(ICConfigurationDescription configDesc, IResource rc, String languageId) {
		List<ICLanguageSettingEntry> entries = null;

		File qmake = new File(qmakePath);
		if (!qmake.exists()
		 || qmakeModTime != qmake.lastModified())
			entries = reload();
		else {
			File qtInstallHeadersDir = new File(qtInstallHeadersPath);
			if (!qtInstallHeadersDir.exists()
			 || qtInstallHeadersModTime != qtInstallHeadersDir.lastModified())
				entries = reload();
		}

		// If the cache was not reloaded, then return the previously discovered entries.
		if (entries == null)
			return super.getSettingEntries(configDesc, rc, languageId);

		// Otherwise store, persist, and return the newly discovered values.
		setSettingEntries(configDesc, rc, languageId, entries);
		serializeLanguageSettingsInBackground(null);
		return entries;
	}

	@Override
	public Element serializeAttributes(Element parentElement) {
		parentElement.setAttribute(ATTR_QMAKE, qmakePath);
		parentElement.setAttribute(ATTR_QMAKE_MOD, Long.toString(qmakeModTime));
		parentElement.setAttribute(ATTR_QT_INSTALL_HEADERS, qtInstallHeadersPath);
		parentElement.setAttribute(ATTR_QT_INSTALL_HEADERS_MOD, Long.toString(qtInstallHeadersModTime));

		// The parent implementation tries to create a new child node (provider) that is used
		// as the part for later entries.  This isn't needed in this case, we just want to
		// use the part that serializes the languages.
		return parentElement;
	}

	@Override
	public void loadAttributes(Element element) {
		qmakeModTime = getLongAttribute(element, ATTR_QMAKE_MOD);
		qtInstallHeadersPath = element.getAttribute(ATTR_QT_INSTALL_HEADERS);
		qtInstallHeadersModTime = getLongAttribute(element, ATTR_QT_INSTALL_HEADERS_MOD);

		// The parent implementation tries to create a new child node (provider) that is used
		// as the part for later entries.  This isn't needed in this case, we just want to
		// use the part that serializes the languages.
	}

	/**
	 * Parse and return the given attribute as a long.  Return 0 if the attribute does
	 * not have a valid value.
	 */
	private static long getLongAttribute(Element element, String attr) {
		String value = element.getAttribute(attr);
		if (value == null
		 || value.isEmpty())
			return 0;

		try {
			return Long.parseLong(value);
		} catch(NumberFormatException e) {
			Activator.log("attribute name:" + attr + " value:" + value, e);
			return 0;
		}
	}

	/**
	 * Reload and return the entries if possible, return null otherwise.
	 */
	private List<ICLanguageSettingEntry> reload() {
		// All keys are reset and then updated as their values are discovered.  This allows partial
		// success to skip over previously calculated values.
		qmakeModTime = 0;
		qtInstallHeadersPath = null;
		qtInstallHeadersModTime = 0;

		File qmake = new File(qmakePath);
		if (!qmake.exists()
		 || !qmake.canExecute())
			return Collections.emptyList();

		qmakeModTime = qmake.lastModified();

		// Run `qmake -query QT_INSTALL_HEADERS` to get output like "/opt/qt-5.0.0/include".
		BufferedReader reader = null;
		Process process = null;
		try {
			process = ProcessFactory.getFactory().exec(new String[]{ qmakePath, "-query", "QT_INSTALL_HEADERS" });
			reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
			qtInstallHeadersPath = reader.readLine();
		} catch(IOException e) {
			Activator.log(e);
		} finally {
			try {
				if (reader != null)
					reader.close();
			} catch(IOException e) {
				/* ignore */
			} finally {
				if (process != null)
					process.destroy();
			}
		}

		if (qtInstallHeadersPath == null)
			return Collections.emptyList();

		File qtInstallHeadersDir = new File(qtInstallHeadersPath);

		qtInstallHeadersModTime = qtInstallHeadersDir.lastModified();
		if (!qtInstallHeadersDir.exists()
		 || !qtInstallHeadersDir.canRead()
		 || !qtInstallHeadersDir.isDirectory())
			return Collections.emptyList();

		// Create an include path entry for all sub-folders in the QT_INSTALL_HEADERS location, including
		// the QT_INSTALL_HEADERS folder itself.
		File[] files = qtInstallHeadersDir.listFiles(new FileFilter() {
			@Override
			public boolean accept(File pathname) {
				return pathname.exists() && pathname.isDirectory();
			}
		});

		List<ICLanguageSettingEntry> entries = new ArrayList<ICLanguageSettingEntry>(files.length + 1);
		safeAdd(entries, qtInstallHeadersDir);
		for(File file : files)
			safeAdd(entries, file);

		return entries;
	}

	private static void safeAdd(List<ICLanguageSettingEntry> entries, File file) {
		try {
			entries.add(new CIncludePathEntry(file.getCanonicalPath(), ICSettingEntry.READONLY | ICSettingEntry.RESOLVED));
		} catch(IOException e) {
			Activator.log(e);
		}
	}
}

Back to the top