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