1 /****************************************************************************
2 **
3 ** Copyright (C) 2016 The Qt Company Ltd.
4 ** Contact: https://www.qt.io/licensing/
5 **
6 ** This file is part of Qt Creator.
7 **
8 ** Commercial License Usage
9 ** Licensees holding valid commercial Qt licenses may use this file in
10 ** accordance with the commercial license agreement provided with the
11 ** Software or, alternatively, in accordance with the terms contained in
12 ** a written agreement between you and The Qt Company. For licensing terms
13 ** and conditions see https://www.qt.io/terms-conditions. For further
14 ** information use the contact form at https://www.qt.io/contact-us.
15 **
16 ** GNU General Public License Usage
17 ** Alternatively, this file may be used under the terms of the GNU
18 ** General Public License version 3 as published by the Free Software
19 ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
20 ** included in the packaging of this file. Please review the following
21 ** information to ensure the GNU General Public License requirements will
22 ** be met: https://www.gnu.org/licenses/gpl-3.0.html.
23 **
24 ****************************************************************************/
25
26 #include "projectimporter.h"
27
28 #include "buildinfo.h"
29 #include "kit.h"
30 #include "kitinformation.h"
31 #include "kitmanager.h"
32 #include "project.h"
33 #include "projectexplorerconstants.h"
34 #include "target.h"
35 #include "toolchain.h"
36 #include "toolchainmanager.h"
37
38 #include <coreplugin/icore.h>
39
40 #include <utils/algorithm.h>
41 #include <utils/environment.h>
42 #include <utils/qtcassert.h>
43
44 #include <QLoggingCategory>
45 #include <QMessageBox>
46 #include <QPushButton>
47 #include <QString>
48
49 namespace ProjectExplorer {
50
51 static const Utils::Id KIT_IS_TEMPORARY("PE.tmp.isTemporary");
52 static const Utils::Id KIT_TEMPORARY_NAME("PE.tmp.Name");
53 static const Utils::Id KIT_FINAL_NAME("PE.tmp.FinalName");
54 static const Utils::Id TEMPORARY_OF_PROJECTS("PE.tmp.ForProjects");
55
fullId(Utils::Id id)56 static Utils::Id fullId(Utils::Id id)
57 {
58 const QString prefix = "PE.tmp.";
59
60 const QString idStr = id.toString();
61 QTC_ASSERT(!idStr.startsWith(prefix), return Utils::Id::fromString(idStr));
62
63 return Utils::Id::fromString(prefix + idStr);
64 }
65
hasOtherUsers(Utils::Id id,const QVariant & v,Kit * k)66 static bool hasOtherUsers(Utils::Id id, const QVariant &v, Kit *k)
67 {
68 return Utils::contains(KitManager::kits(), [id, v, k](Kit *in) -> bool {
69 if (in == k)
70 return false;
71 const QVariantList tmp = in->value(id).toList();
72 return tmp.contains(v);
73 });
74 }
75
ProjectImporter(const Utils::FilePath & path)76 ProjectImporter::ProjectImporter(const Utils::FilePath &path) : m_projectPath(path)
77 {
78 useTemporaryKitAspect(ToolChainKitAspect::id(),
79 [this](Kit *k, const QVariantList &vl) { cleanupTemporaryToolChains(k, vl); },
80 [this](Kit *k, const QVariantList &vl) { persistTemporaryToolChains(k, vl); });
81 }
82
~ProjectImporter()83 ProjectImporter::~ProjectImporter()
84 {
85 foreach (Kit *k, KitManager::kits())
86 removeProject(k);
87 }
88
import(const Utils::FilePath & importPath,bool silent)89 const QList<BuildInfo> ProjectImporter::import(const Utils::FilePath &importPath, bool silent)
90 {
91 QList<BuildInfo> result;
92
93 const QLoggingCategory log("qtc.projectexplorer.import", QtWarningMsg);
94 qCDebug(log) << "ProjectImporter::import" << importPath << silent;
95
96 QFileInfo fi = importPath.toFileInfo();
97 if (!fi.exists() && !fi.isDir()) {
98 qCDebug(log) << "**doesn't exist";
99 return result;
100 }
101
102 const Utils::FilePath absoluteImportPath = Utils::FilePath::fromString(fi.absoluteFilePath());
103
104 const auto handleFailure = [this, importPath, silent] {
105 if (silent)
106 return;
107 QMessageBox::critical(Core::ICore::dialogParent(),
108 tr("No Build Found"),
109 tr("No build found in %1 matching project %2.")
110 .arg(importPath.toUserOutput(), projectFilePath().toUserOutput()));
111 };
112 qCDebug(log) << "Examining directory" << absoluteImportPath.toString();
113 QString warningMessage;
114 QList<void *> dataList = examineDirectory(absoluteImportPath, &warningMessage);
115 if (dataList.isEmpty()) {
116 qCDebug(log) << "Nothing to import found in" << absoluteImportPath.toString();
117 handleFailure();
118 return result;
119 }
120 if (!warningMessage.isEmpty()) {
121 qCDebug(log) << "Warning when examining" << absoluteImportPath.toString();
122 // we should ask user before importing
123 if (silent)
124 return result;
125 QMessageBox dialog(Core::ICore::dialogParent());
126 dialog.setWindowTitle(tr("Import Warning"));
127 dialog.setText(warningMessage);
128 dialog.setIcon(QMessageBox::Warning);
129 QPushButton *acceptButton = dialog.addButton(tr("Import Build"), QMessageBox::AcceptRole);
130 dialog.addButton(QMessageBox::Cancel);
131 dialog.exec();
132 if (dialog.clickedButton() != acceptButton)
133 return result;
134 }
135
136 qCDebug(log) << "Looking for kits";
137 foreach (void *data, dataList) {
138 QTC_ASSERT(data, continue);
139 QList<Kit *> kitList;
140 const QList<Kit *> tmp
141 = Utils::filtered(KitManager::kits(), [this, data](Kit *k) { return matchKit(data, k); });
142 if (tmp.isEmpty()) {
143 Kit *k = createKit(data);
144 if (k)
145 kitList.append(k);
146 qCDebug(log) << " no matching kit found, temporary kit created.";
147 } else {
148 kitList += tmp;
149 qCDebug(log) << " " << tmp.count() << "matching kits found.";
150 }
151
152 foreach (Kit *k, kitList) {
153 qCDebug(log) << "Creating buildinfos for kit" << k->displayName();
154 const QList<BuildInfo> infoList = buildInfoList(data);
155 if (infoList.isEmpty()) {
156 qCDebug(log) << "No build infos for kit" << k->displayName();
157 continue;
158 }
159
160 auto factory = BuildConfigurationFactory::find(k, projectFilePath());
161 for (BuildInfo i : infoList) {
162 i.kitId = k->id();
163 i.factory = factory;
164 if (!result.contains(i))
165 result += i;
166 }
167 }
168 }
169
170 foreach (auto *dd, dataList)
171 deleteDirectoryData(dd);
172 dataList.clear();
173
174 if (result.isEmpty())
175 handleFailure();
176
177 return result;
178 }
179
preferredTarget(const QList<Target * > & possibleTargets)180 Target *ProjectImporter::preferredTarget(const QList<Target *> &possibleTargets)
181 {
182 // Select active target
183 // a) The default target
184 // c) Desktop target
185 // d) the first target
186 Target *activeTarget = nullptr;
187 if (possibleTargets.isEmpty())
188 return activeTarget;
189
190 activeTarget = possibleTargets.at(0);
191 bool pickedFallback = false;
192 foreach (Target *t, possibleTargets) {
193 if (t->kit() == KitManager::defaultKit())
194 return t;
195 if (pickedFallback)
196 continue;
197 if (DeviceTypeKitAspect::deviceTypeId(t->kit()) == Constants::DESKTOP_DEVICE_TYPE) {
198 activeTarget = t;
199 pickedFallback = true;
200 }
201 }
202 return activeTarget;
203 }
204
markKitAsTemporary(Kit * k) const205 void ProjectImporter::markKitAsTemporary(Kit *k) const
206 {
207 QTC_ASSERT(!k->hasValue(KIT_IS_TEMPORARY), return);
208
209 UpdateGuard guard(*this);
210
211 const QString name = k->displayName();
212 k->setUnexpandedDisplayName(QCoreApplication::translate("ProjectExplorer::ProjectImporter",
213 "%1 - temporary").arg(name));
214
215 k->setValue(KIT_TEMPORARY_NAME, k->displayName());
216 k->setValue(KIT_FINAL_NAME, name);
217 k->setValue(KIT_IS_TEMPORARY, true);
218 }
219
makePersistent(Kit * k) const220 void ProjectImporter::makePersistent(Kit *k) const
221 {
222 QTC_ASSERT(k, return);
223 if (!k->hasValue(KIT_IS_TEMPORARY))
224 return;
225
226 UpdateGuard guard(*this);
227
228 KitGuard kitGuard(k);
229 k->removeKey(KIT_IS_TEMPORARY);
230 k->removeKey(TEMPORARY_OF_PROJECTS);
231 const QString tempName = k->value(KIT_TEMPORARY_NAME).toString();
232 if (!tempName.isNull() && k->displayName() == tempName)
233 k->setUnexpandedDisplayName(k->value(KIT_FINAL_NAME).toString());
234 k->removeKey(KIT_TEMPORARY_NAME);
235 k->removeKey(KIT_FINAL_NAME);
236
237 foreach (const TemporaryInformationHandler &tih, m_temporaryHandlers) {
238 const Utils::Id fid = fullId(tih.id);
239 const QVariantList temporaryValues = k->value(fid).toList();
240
241 // Mark permanent in all other kits:
242 foreach (Kit *ok, KitManager::kits()) {
243 if (ok == k || !ok->hasValue(fid))
244 continue;
245 const QVariantList otherTemporaryValues
246 = Utils::filtered(ok->value(fid).toList(), [&temporaryValues](const QVariant &v) {
247 return !temporaryValues.contains(v);
248 });
249 ok->setValueSilently(fid, otherTemporaryValues);
250 }
251
252 // persist:
253 tih.persist(k, temporaryValues);
254 k->removeKeySilently(fid);
255 }
256 }
257
cleanupKit(Kit * k) const258 void ProjectImporter::cleanupKit(Kit *k) const
259 {
260 QTC_ASSERT(k, return);
261 foreach (const TemporaryInformationHandler &tih, m_temporaryHandlers) {
262 const Utils::Id fid = fullId(tih.id);
263 const QVariantList temporaryValues
264 = Utils::filtered(k->value(fid).toList(), [fid, k](const QVariant &v) {
265 return !hasOtherUsers(fid, v, k);
266 });
267 tih.cleanup(k, temporaryValues);
268 k->removeKeySilently(fid);
269 }
270
271 // remove keys to manage temporary state of kit:
272 k->removeKeySilently(KIT_IS_TEMPORARY);
273 k->removeKeySilently(TEMPORARY_OF_PROJECTS);
274 k->removeKeySilently(KIT_FINAL_NAME);
275 k->removeKeySilently(KIT_TEMPORARY_NAME);
276 }
277
addProject(Kit * k) const278 void ProjectImporter::addProject(Kit *k) const
279 {
280 QTC_ASSERT(k, return);
281 if (!k->hasValue(KIT_IS_TEMPORARY))
282 return;
283
284 UpdateGuard guard(*this);
285 QStringList projects = k->value(TEMPORARY_OF_PROJECTS, QStringList()).toStringList();
286 projects.append(m_projectPath.toString()); // note: There can be more than one instance of the project added!
287 k->setValueSilently(TEMPORARY_OF_PROJECTS, projects);
288 }
289
removeProject(Kit * k) const290 void ProjectImporter::removeProject(Kit *k) const
291 {
292 QTC_ASSERT(k, return);
293 if (!k->hasValue(KIT_IS_TEMPORARY))
294 return;
295
296 UpdateGuard guard(*this);
297 QStringList projects = k->value(TEMPORARY_OF_PROJECTS, QStringList()).toStringList();
298 projects.removeOne(m_projectPath.toString());
299
300 if (projects.isEmpty()) {
301 cleanupKit(k);
302 KitManager::deregisterKit(k);
303 } else {
304 k->setValueSilently(TEMPORARY_OF_PROJECTS, projects);
305 }
306 }
307
isTemporaryKit(Kit * k) const308 bool ProjectImporter::isTemporaryKit(Kit *k) const
309 {
310 QTC_ASSERT(k, return false);
311 return k->hasValue(KIT_IS_TEMPORARY);
312 }
313
createTemporaryKit(const KitSetupFunction & setup) const314 Kit *ProjectImporter::createTemporaryKit(const KitSetupFunction &setup) const
315 {
316 UpdateGuard guard(*this);
317 const auto init = [&](Kit *k) {
318 KitGuard kitGuard(k);
319 k->setUnexpandedDisplayName(QCoreApplication::translate("ProjectExplorer::ProjectImporter",
320 "Imported Kit"));
321 k->setup();
322 setup(k);
323 k->fix();
324 markKitAsTemporary(k);
325 addProject(k);
326 }; // ~KitGuard, sending kitUpdated
327 return KitManager::registerKit(init); // potentially adds kits to other targetsetuppages
328 }
329
findTemporaryHandler(Utils::Id id) const330 bool ProjectImporter::findTemporaryHandler(Utils::Id id) const
331 {
332 return Utils::contains(m_temporaryHandlers, [id](const TemporaryInformationHandler &ch) { return ch.id == id; });
333 }
334
toolChainFromVariant(const QVariant & v)335 static ToolChain *toolChainFromVariant(const QVariant &v)
336 {
337 const QByteArray tcId = v.toByteArray();
338 return ToolChainManager::findToolChain(tcId);
339 }
340
cleanupTemporaryToolChains(Kit * k,const QVariantList & vl)341 void ProjectImporter::cleanupTemporaryToolChains(Kit *k, const QVariantList &vl)
342 {
343 for (const QVariant &v : vl) {
344 ToolChain *tc = toolChainFromVariant(v);
345 QTC_ASSERT(tc, continue);
346 ToolChainManager::deregisterToolChain(tc);
347 ToolChainKitAspect::setToolChain(k, nullptr);
348 }
349 }
350
persistTemporaryToolChains(Kit * k,const QVariantList & vl)351 void ProjectImporter::persistTemporaryToolChains(Kit *k, const QVariantList &vl)
352 {
353 for (const QVariant &v : vl) {
354 ToolChain *tmpTc = toolChainFromVariant(v);
355 QTC_ASSERT(tmpTc, continue);
356 ToolChain *actualTc = ToolChainKitAspect::toolChain(k, tmpTc->language());
357 if (tmpTc && actualTc != tmpTc)
358 ToolChainManager::deregisterToolChain(tmpTc);
359 }
360 }
361
useTemporaryKitAspect(Utils::Id id,ProjectImporter::CleanupFunction cleanup,ProjectImporter::PersistFunction persist)362 void ProjectImporter::useTemporaryKitAspect(Utils::Id id,
363 ProjectImporter::CleanupFunction cleanup,
364 ProjectImporter::PersistFunction persist)
365 {
366 QTC_ASSERT(!findTemporaryHandler(id), return);
367 m_temporaryHandlers.append({id, cleanup, persist});
368 }
369
addTemporaryData(Utils::Id id,const QVariant & cleanupData,Kit * k) const370 void ProjectImporter::addTemporaryData(Utils::Id id, const QVariant &cleanupData, Kit *k) const
371 {
372 QTC_ASSERT(k, return);
373 QTC_ASSERT(findTemporaryHandler(id), return);
374 const Utils::Id fid = fullId(id);
375
376 KitGuard guard(k);
377 QVariantList tmp = k->value(fid).toList();
378 QTC_ASSERT(!tmp.contains(cleanupData), return);
379 tmp.append(cleanupData);
380 k->setValue(fid, tmp);
381 }
382
hasKitWithTemporaryData(Utils::Id id,const QVariant & data) const383 bool ProjectImporter::hasKitWithTemporaryData(Utils::Id id, const QVariant &data) const
384 {
385 Utils::Id fid = fullId(id);
386 return Utils::contains(KitManager::kits(), [data, fid](Kit *k) {
387 return k->value(fid).toList().contains(data);
388 });
389 }
390
createToolChains(const ToolChainDescription & tcd)391 static ProjectImporter::ToolChainData createToolChains(const ToolChainDescription &tcd)
392 {
393 ProjectImporter::ToolChainData data;
394
395 for (ToolChainFactory *factory : ToolChainFactory::allToolChainFactories()) {
396 data.tcs = factory->detectForImport(tcd);
397 if (data.tcs.isEmpty())
398 continue;
399
400 for (ToolChain *tc : qAsConst(data.tcs))
401 ToolChainManager::registerToolChain(tc);
402
403 data.areTemporary = true;
404 break;
405 }
406
407 return data;
408 }
409
410 ProjectImporter::ToolChainData
findOrCreateToolChains(const ToolChainDescription & tcd) const411 ProjectImporter::findOrCreateToolChains(const ToolChainDescription &tcd) const
412 {
413 ToolChainData result;
414 result.tcs = ToolChainManager::toolChains([&tcd](const ToolChain *tc) {
415 return tc->language() == tcd.language &&
416 Utils::Environment::systemEnvironment().isSameExecutable(
417 tc->compilerCommand().toString(), tcd.compilerPath.toString());
418 });
419 for (const ToolChain *tc : qAsConst(result.tcs)) {
420 const QByteArray tcId = tc->id();
421 result.areTemporary = result.areTemporary ? true : hasKitWithTemporaryData(ToolChainKitAspect::id(), tcId);
422 }
423 if (!result.tcs.isEmpty())
424 return result;
425
426 // Create a new toolchain:
427 UpdateGuard guard(*this);
428 return createToolChains(tcd);
429 }
430
431 } // namespace ProjectExplorer
432