1 /****************************************************************************
2 **
3 ** Copyright (C) 2018 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 "cmaketoolsettingsaccessor.h"
27 
28 #include "cmaketool.h"
29 #include "cmaketoolmanager.h"
30 
31 #include <coreplugin/icore.h>
32 
33 #include <app/app_version.h>
34 #include <utils/environment.h>
35 
36 #include <utils/algorithm.h>
37 
38 #include <QDebug>
39 #include <QDir>
40 #include <QFileInfo>
41 
42 using namespace Utils;
43 
44 namespace CMakeProjectManager {
45 namespace Internal {
46 
47 // --------------------------------------------------------------------
48 // CMakeToolSettingsUpgraders:
49 // --------------------------------------------------------------------
50 
51 class CMakeToolSettingsUpgraderV0 : public VersionUpgrader
52 {
53     // Necessary to make Version 1 supported.
54 public:
CMakeToolSettingsUpgraderV0()55     CMakeToolSettingsUpgraderV0() : VersionUpgrader(0, "4.6") { }
56 
57     // NOOP
upgrade(const QVariantMap & data)58     QVariantMap upgrade(const QVariantMap &data) final { return data; }
59 };
60 
61 // --------------------------------------------------------------------
62 // Helpers:
63 // --------------------------------------------------------------------
64 
65 const char CMAKE_TOOL_COUNT_KEY[] = "CMakeTools.Count";
66 const char CMAKE_TOOL_DATA_KEY[] = "CMakeTools.";
67 const char CMAKE_TOOL_DEFAULT_KEY[] = "CMakeTools.Default";
68 const char CMAKE_TOOL_FILENAME[] = "cmaketools.xml";
69 
autoDetectCMakeTools()70 static std::vector<std::unique_ptr<CMakeTool>> autoDetectCMakeTools()
71 {
72     Environment env = Environment::systemEnvironment();
73 
74     FilePaths path = env.path();
75     path = Utils::filteredUnique(path);
76 
77     if (HostOsInfo::isWindowsHost()) {
78         for (auto envVar : {"ProgramFiles", "ProgramFiles(x86)", "ProgramW6432"}) {
79             if (qEnvironmentVariableIsSet(envVar)) {
80                 const QString progFiles = qEnvironmentVariable(envVar);
81                 path.append(FilePath::fromString(progFiles + "/CMake"));
82                 path.append(FilePath::fromString(progFiles + "/CMake/bin"));
83             }
84         }
85     }
86 
87     if (HostOsInfo::isMacHost()) {
88         path.append(FilePath::fromString("/Applications/CMake.app/Contents/bin"));
89         path.append(FilePath::fromString("/usr/local/bin"));
90         path.append(FilePath::fromString("/opt/local/bin"));
91     }
92 
93     const QStringList execs = env.appendExeExtensions(QLatin1String("cmake"));
94 
95     FilePaths suspects;
96     foreach (const FilePath &base, path) {
97         if (base.isEmpty())
98             continue;
99 
100         QFileInfo fi;
101         for (const QString &exec : execs) {
102             fi.setFile(QDir(base.toString()), exec);
103             if (fi.exists() && fi.isFile() && fi.isExecutable())
104                 suspects << FilePath::fromString(fi.absoluteFilePath());
105         }
106     }
107 
108     std::vector<std::unique_ptr<CMakeTool>> found;
109     foreach (const FilePath &command, suspects) {
110         auto item = std::make_unique<CMakeTool>(CMakeTool::AutoDetection, CMakeTool::createId());
111         item->setFilePath(command);
112         item->setDisplayName(CMakeToolManager::tr("System CMake at %1").arg(command.toUserOutput()));
113 
114         found.emplace_back(std::move(item));
115     }
116 
117     return found;
118 }
119 
120 
121 static std::vector<std::unique_ptr<CMakeTool>>
mergeTools(std::vector<std::unique_ptr<CMakeTool>> & sdkTools,std::vector<std::unique_ptr<CMakeTool>> & userTools,std::vector<std::unique_ptr<CMakeTool>> & autoDetectedTools)122 mergeTools(std::vector<std::unique_ptr<CMakeTool>> &sdkTools,
123            std::vector<std::unique_ptr<CMakeTool>> &userTools,
124            std::vector<std::unique_ptr<CMakeTool>> &autoDetectedTools)
125 {
126     std::vector<std::unique_ptr<CMakeTool>> result = std::move(sdkTools);
127     while (userTools.size() > 0) {
128         std::unique_ptr<CMakeTool> userTool = std::move(userTools[0]);
129         userTools.erase(std::begin(userTools));
130 
131         int userToolIndex = Utils::indexOf(result, Utils::equal(&CMakeTool::id, userTool->id()));
132         if (userToolIndex >= 0) {
133             // Replace the sdk tool with the user tool, so any user changes do not get lost
134             result[userToolIndex] = std::move(userTool);
135         } else {
136             if (userTool->isAutoDetected()
137                     && !Utils::contains(autoDetectedTools, Utils::equal(&CMakeTool::cmakeExecutable,
138                                                                         userTool->cmakeExecutable()))) {
139 
140                 qWarning() << QString::fromLatin1("Previously SDK provided CMakeTool \"%1\" (%2) dropped.")
141                               .arg(userTool->cmakeExecutable().toUserOutput(), userTool->id().toString());
142                 continue;
143             }
144             result.emplace_back(std::move(userTool));
145         }
146     }
147 
148     // add all the autodetected tools that are not known yet
149     while (autoDetectedTools.size() > 0) {
150         std::unique_ptr<CMakeTool> autoDetectedTool = std::move(autoDetectedTools[0]);
151         autoDetectedTools.erase(std::begin(autoDetectedTools));
152 
153         if (!Utils::contains(result,
154                              Utils::equal(&CMakeTool::cmakeExecutable, autoDetectedTool->cmakeExecutable())))
155             result.emplace_back(std::move(autoDetectedTool));
156     }
157 
158     return result;
159 }
160 
161 
162 // --------------------------------------------------------------------
163 // CMakeToolSettingsAccessor:
164 // --------------------------------------------------------------------
165 
CMakeToolSettingsAccessor()166 CMakeToolSettingsAccessor::CMakeToolSettingsAccessor() :
167     UpgradingSettingsAccessor("QtCreatorCMakeTools",
168                               QCoreApplication::translate("CMakeProjectManager::CMakeToolManager", "CMake"),
169                               Core::Constants::IDE_DISPLAY_NAME)
170 {
171     setBaseFilePath(Core::ICore::userResourcePath(CMAKE_TOOL_FILENAME));
172 
173     addVersionUpgrader(std::make_unique<CMakeToolSettingsUpgraderV0>());
174 }
175 
restoreCMakeTools(QWidget * parent) const176 CMakeToolSettingsAccessor::CMakeTools CMakeToolSettingsAccessor::restoreCMakeTools(QWidget *parent) const
177 {
178     CMakeTools result;
179 
180     const FilePath sdkSettingsFile = Core::ICore::installerResourcePath(CMAKE_TOOL_FILENAME);
181 
182     CMakeTools sdkTools = cmakeTools(restoreSettings(sdkSettingsFile, parent), true);
183 
184     //read the tools from the user settings file
185     CMakeTools userTools = cmakeTools(restoreSettings(parent), false);
186 
187     //autodetect tools
188     std::vector<std::unique_ptr<CMakeTool>> autoDetectedTools = autoDetectCMakeTools();
189 
190     //filter out the tools that were stored in SDK
191     std::vector<std::unique_ptr<CMakeTool>> toRegister = mergeTools(sdkTools.cmakeTools,
192                                                                     userTools.cmakeTools,
193                                                                     autoDetectedTools);
194 
195     // Store all tools
196     for (auto it = std::begin(toRegister); it != std::end(toRegister); ++it)
197         result.cmakeTools.emplace_back(std::move(*it));
198 
199     result.defaultToolId = userTools.defaultToolId.isValid() ? userTools.defaultToolId : sdkTools.defaultToolId;
200 
201     // Set default TC...
202     return result;
203 }
204 
saveCMakeTools(const QList<CMakeTool * > & cmakeTools,const Id & defaultId,QWidget * parent)205 void CMakeToolSettingsAccessor::saveCMakeTools(const QList<CMakeTool *> &cmakeTools,
206                                                const Id &defaultId,
207                                                QWidget *parent)
208 {
209     QVariantMap data;
210     data.insert(QLatin1String(CMAKE_TOOL_DEFAULT_KEY), defaultId.toSetting());
211 
212     int count = 0;
213     for (const CMakeTool *item : cmakeTools) {
214         Utils::FilePath fi = item->cmakeExecutable();
215 
216         if (fi.isExecutableFile()) {
217             QVariantMap tmp = item->toMap();
218             if (tmp.isEmpty())
219                 continue;
220             data.insert(QString::fromLatin1(CMAKE_TOOL_DATA_KEY) + QString::number(count), tmp);
221             ++count;
222         }
223     }
224     data.insert(QLatin1String(CMAKE_TOOL_COUNT_KEY), count);
225 
226     saveSettings(data, parent);
227 }
228 
229 CMakeToolSettingsAccessor::CMakeTools
cmakeTools(const QVariantMap & data,bool fromSdk) const230 CMakeToolSettingsAccessor::cmakeTools(const QVariantMap &data, bool fromSdk) const
231 {
232     CMakeTools result;
233 
234     int count = data.value(QLatin1String(CMAKE_TOOL_COUNT_KEY), 0).toInt();
235     for (int i = 0; i < count; ++i) {
236         const QString key = QString::fromLatin1(CMAKE_TOOL_DATA_KEY) + QString::number(i);
237         if (!data.contains(key))
238             continue;
239 
240         const QVariantMap dbMap = data.value(key).toMap();
241         auto item = std::make_unique<CMakeTool>(dbMap, fromSdk);
242         if (item->isAutoDetected() && !item->cmakeExecutable().isExecutableFile()) {
243             qWarning() << QString::fromLatin1("CMakeTool \"%1\" (%2) dropped since the command is not executable.")
244                           .arg(item->cmakeExecutable().toUserOutput(), item->id().toString());
245             continue;
246         }
247 
248         result.cmakeTools.emplace_back(std::move(item));
249     }
250 
251     result.defaultToolId = Id::fromSetting(data.value(CMAKE_TOOL_DEFAULT_KEY, Id().toSetting()));
252 
253     return result;
254 }
255 
256 } // namespace Internal
257 } // namespace CMakeProjectManager
258