ssu
Loading...
Searching...
No Matches
ssukickstarter.cpp
Go to the documentation of this file.
1
7
8#include <QStringList>
9#include <QRegExp>
10#include <QDirIterator>
11
12#include "ssukickstarter.h"
13#include "libssu/sandbox_p.h"
16
17#include "../constants.h"
18
19/* TODO:
20 * - commands from the command section should be verified
21 */
22
23
24SsuKickstarter::SsuKickstarter()
25{
26 SsuDeviceInfo deviceInfo;
27 deviceModel = deviceInfo.deviceModel();
28
29 if ((ssu.deviceMode() & Ssu::RndMode) == Ssu::RndMode)
30 rndMode = true;
31 else
32 rndMode = false;
33}
34
35QStringList SsuKickstarter::commands()
36{
37 SsuDeviceInfo deviceInfo(deviceModel);
38 QStringList result;
39
40 QHash<QString, QString> h;
41
42 deviceInfo.variableSection("kickstart-commands", &h);
43
44 // read commands from variable, ...
45
46 QHash<QString, QString>::const_iterator it = h.constBegin();
47 while (it != h.constEnd()) {
48 result.append(it.key() + " " + it.value());
49 it++;
50 }
51
52 return result;
53}
54
55QStringList SsuKickstarter::commandSection(const QString &section, const QString &description)
56{
57 QStringList result;
58 SsuDeviceInfo deviceInfo(deviceModel);
59 QString commandFile;
60 QFile part;
61
62 QDir dir(Sandbox::map(QString("/%1/kickstart/%2/")
63 .arg(SSU_DATA_DIR)
64 .arg(section)));
65
66 if (dir.exists(replaceSpaces(deviceModel.toLower()))) {
67 commandFile = replaceSpaces(deviceModel.toLower());
68 } else if (dir.exists(replaceSpaces(deviceInfo.deviceVariant(true).toLower()))) {
69 commandFile = replaceSpaces(deviceInfo.deviceVariant(true).toLower());
70 } else if (dir.exists("default")) {
71 commandFile = "default";
72 } else {
73 if (description.isEmpty())
74 result.append("## No suitable configuration found in " + dir.path());
75 else
76 result.append("## No configuration for " + description + " found.");
77 return result;
78 }
79
80 QFile file(dir.path() + "/" + commandFile);
81
82 if (description.isEmpty())
83 result.append("### Commands from " + dir.path() + "/" + commandFile);
84 else
85 result.append("### " + description + " from " + commandFile);
86
87 if (file.open(QIODevice::ReadOnly | QIODevice::Text)) {
88 QTextStream in(&file);
89 while (!in.atEnd())
90 result.append(in.readLine());
91 }
92
93 return result;
94}
95
96QString SsuKickstarter::replaceSpaces(const QString &value)
97{
98 QString retval = value;
99 return retval.replace(" ", "_");
100}
101
102QStringList SsuKickstarter::repos()
103{
104 QStringList result;
105 SsuDeviceInfo deviceInfo(deviceModel);
106 SsuRepoManager repoManager;
107 QTextStream qerr(stderr);
108
109 QStringList repos = repoManager.repos(rndMode, deviceInfo, Ssu::BoardFilter);
110
111 foreach (const QString &repo, repos) {
112 QString repoUrl = ssu.repoUrl(repo, rndMode, QHash<QString, QString>(), repoOverride);
113
114 if (repoUrl.isEmpty()) {
115 qerr << "Repository " << repo << " does not have an URL, ignoring" << endl;
116 continue;
117 }
118
119 // Adaptation repos need to have separate naming so that when images are done
120 // the repository caches will not be mixed with each other.
121 if (repo.startsWith("adaptation")) {
122 result.append(QString("repo --name=%1-%2-%3%4 --baseurl=%5")
123 .arg(repo)
124 .arg(replaceSpaces(deviceModel))
125 .arg(repoOverride.value("release"))
126 .arg((rndMode ? "-" + repoOverride.value("flavour")
127 : QString()))
128 .arg(repoUrl)
129 );
130 } else {
131 result.append(QString("repo --name=%1-%2%3 --baseurl=%4")
132 .arg(repo)
133 .arg(repoOverride.value("release"))
134 .arg((rndMode ? "-" + repoOverride.value("flavour")
135 : QString()))
136 .arg(repoUrl)
137 );
138 }
139 }
140
141 return result;
142}
143
144QStringList SsuKickstarter::packagesSection(const QString &name)
145{
146 QStringList result;
147
148 if (name == "packages") {
149 // insert patterns-sailfish-device-configuration-<device>
150 QString dashedDeviceModel = deviceModel;
151 dashedDeviceModel.replace(' ', '-');
152 QString configuration = QString("patterns-sailfish-device-configuration-%1")
153 .arg(dashedDeviceModel);
154 result.append(configuration);
155
156 result.sort();
157 result.removeDuplicates();
158 } else {
159 result = commandSection(name);
160 }
161
162 result.prepend("%" + name);
163 result.append("%end");
164 return result;
165}
166
167// we intentionally don't support device-specific post scriptlets
168QStringList SsuKickstarter::scriptletSection(const QString &name, int flags)
169{
170 QStringList result;
171 QString path;
172 QDir dir;
173
174 if ((flags & NoChroot) == NoChroot)
175 path = Sandbox::map(QString("/%1/kickstart/%2_nochroot/")
176 .arg(SSU_DATA_DIR)
177 .arg(name));
178 else
179 path = Sandbox::map(QString("/%1/kickstart/%2/")
180 .arg(SSU_DATA_DIR)
181 .arg(name));
182
183 if ((flags & DeviceSpecific) == DeviceSpecific) {
184 if (dir.exists(path + "/" + replaceSpaces(deviceModel.toLower())))
185 path = path + "/" + replaceSpaces(deviceModel.toLower());
186 else
187 path = path + "/default";
188 }
189
190 dir.setPath(path);
191 QStringList scriptlets = dir.entryList(QDir::AllEntries | QDir::NoDot | QDir::NoDotDot,
192 QDir::Name);
193
194 foreach (const QString &scriptlet, scriptlets) {
195 QFile file(dir.filePath(scriptlet));
196 result.append("### begin " + scriptlet);
197 if (file.open(QIODevice::ReadOnly | QIODevice::Text)) {
198 QTextStream in(&file);
199 while (!in.atEnd())
200 result.append(in.readLine());
201 }
202 result.append("### end " + scriptlet);
203 }
204
205 if (!result.isEmpty()) {
206 result.prepend(QString("export SSU_RELEASE_TYPE=%1")
207 .arg(rndMode ? "rnd" : "release"));
208
209 if ((flags & NoChroot) == NoChroot)
210 result.prepend("%" + name + " --nochroot --erroronfail");
211 else
212 result.prepend("%" + name + " --erroronfail");
213
214 result.append("%end");
215 }
216
217 return result;
218}
219
220void SsuKickstarter::setRepoParameters(QHash<QString, QString> parameters)
221{
222 repoOverride = parameters;
223
224 if (repoOverride.contains("model"))
225 deviceModel = repoOverride.value("model");
226}
227
228bool SsuKickstarter::write(const QString &kickstart)
229{
230 QTextStream qerr(stderr);
231 QStringList commandSections;
232
233 // initialize with default 'part' for compatibility, as partitions
234 // used to work without configuration. It'll get replaced with
235 // configuration values, if found
236 commandSections.append("part");
237
238 // rnd mode should not come from the defaults
239 if (repoOverride.contains("rnd")) {
240 if (repoOverride.value("rnd") == "true")
241 rndMode = true;
242 else if (repoOverride.value("rnd") == "false")
243 rndMode = false;
244 }
245
246 if (repoOverride.contains("domain")) {
247 ssu.setDomain(repoOverride.value("domain"));
248 }
249
250 QHash<QString, QString> defaults;
251 // get generic repo variables; adaptation specific bits are not interesting in the kickstart
252 SsuRepoManager repoManager;
253 repoManager.repoVariables(&defaults, rndMode);
254
255 // overwrite with kickstart defaults
256 SsuDeviceInfo deviceInfo(deviceModel);
257 deviceInfo.variableSection("kickstart-defaults", &defaults);
258 if (deviceInfo.variable("kickstart-defaults", "commandSections")
259 .canConvert(QMetaType::QStringList)) {
260 commandSections =
261 deviceInfo.variable("kickstart-defaults", "commandSections").toStringList();
262 }
263
264 QHash<QString, QString>::const_iterator it = defaults.constBegin();
265 while (it != defaults.constEnd()) {
266 if (!repoOverride.contains(it.key()))
267 repoOverride.insert(it.key(), it.value());
268 it++;
269 }
270
271 if (rndMode) {
272 // Ensure flavour is set correctly
273 if (repoOverride.contains("flavour")) {
274 repoOverride.insert("flavourName", repoOverride.value("flavour"));
275 } else if (repoOverride.contains("flavourName")) {
276 qerr << "Warning: Should use flavour instead of flavourName" << endl;
277 repoOverride.insert("flavour", repoOverride.value("flavourName"));
278 } else {
279 qerr << "Warning: RND mode but flavour is not set" << endl;
280 }
281 } else {
282 // release mode variables should not contain flavour
283 if (repoOverride.remove("flavour")) {
284 qerr << "Warning: Unset flavour in release mode." << endl;
285 }
286 if (repoOverride.remove("flavourName")) {
287 qerr << "Warning: Unset flavourName in release mode." << endl;
288 }
289 }
290
291 //TODO: check for mandatory keys, ..
292 if (!repoOverride.contains("deviceModel"))
293 repoOverride.insert("deviceModel", deviceInfo.deviceModel());
294
295 // do sanity checking on the model
296 if (deviceInfo.contains() == false) {
297 qerr << "Device model '" << deviceInfo.deviceModel() << "' does not exist" << endl;
298
299 if (repoOverride.value("force") != "true")
300 return false;
301 }
302
303 QRegExp regex(" {2,}", Qt::CaseSensitive, QRegExp::RegExp2);
304 if (regex.indexIn(deviceInfo.deviceModel(), 0) != -1) {
305 qerr << "Device model '" << deviceInfo.deviceModel()
306 << "' contains multiple consecutive spaces." << endl;
307 if (deviceInfo.contains())
308 qerr << "Since the model exists it looks like your configuration is broken." << endl;
309 return false;
310 }
311
312 if (!repoOverride.contains("brand")) {
313 qerr << "Brand is missing in your configuration." << endl;
314 return false;
315 }
316
317 bool opened = false;
318 QString outputDir = repoOverride.value("outputdir");
319 if (!outputDir.isEmpty()) outputDir.append("/");
320
321 QFile ks;
322
323 if (kickstart.isEmpty()) {
324 SsuVariables var;
325
326 if (repoOverride.contains("filename")) {
327 QString fileName = QString("%1%2")
328 .arg(outputDir)
329 .arg(replaceSpaces(var.resolveString(repoOverride.value("filename"),
330 &repoOverride)));
331
332 ks.setFileName(fileName);
333 opened = ks.open(QIODevice::WriteOnly);
334 } else {
335 qerr << "No filename specified, and no default filename configured" << endl;
336 return false;
337 }
338 } else if (kickstart == "-") {
339 opened = ks.open(stdout, QIODevice::WriteOnly);
340 } else {
341 ks.setFileName(outputDir + kickstart);
342 opened = ks.open(QIODevice::WriteOnly);
343 }
344
345 if (!opened) {
346 qerr << "Unable to write output file " << ks.fileName() << ": " << ks.errorString() << endl;
347 return false;
348 } else if (!ks.fileName().isEmpty()) {
349 qerr << "Writing kickstart to " << ks.fileName() << endl;
350 }
351
352 QString displayName = QString("# DisplayName: %1 %2/%3 (%4) %5")
353 .arg(repoOverride.value("brand"))
354 .arg(deviceInfo.deviceModel())
355 .arg(repoOverride.value("arch"))
356 .arg((rndMode ? "rnd"
357 : "release"))
358 .arg(repoOverride.value("version"));
359
360 // Feature names can be prefixed with '-' to inhibit implicit suggestion
361 QStringList featuresList = deviceInfo.value("img-features").toStringList();
362
363 // Add developer-mode feature to rnd images by default
364 if (rndMode && !featuresList.contains("-developer-mode"))
365 featuresList << "developer-mode";
366
367 featuresList = featuresList.filter(QRegExp("^[^-]"));
368
369 QString suggestedFeatures;
370
371 // work around some idiotic JS list parsing on our side by terminating one-element list by comma
372 if (featuresList.count() == 1)
373 suggestedFeatures = QString("# SuggestedFeatures: %1,")
374 .arg(featuresList.join(", "));
375 else if (featuresList.count() > 1)
376 suggestedFeatures = QString("# SuggestedFeatures: %1")
377 .arg(featuresList.join(", "));
378
379 QString imageType = "fs";
380 if (!deviceInfo.value("img-type").toString().isEmpty())
381 imageType = deviceInfo.value("img-type").toString();
382
383 QString imageArchitecture = "armv7hl";
384 if (!deviceInfo.value("img-arch").toString().isEmpty())
385 imageArchitecture = deviceInfo.value("img-arch").toString();
386
387 QString kickstartType = QString("# KickstartType: %1")
388 .arg((rndMode ? "rnd" : "release"));
389
390 const QString deviceModel = deviceInfo.deviceModel();
391
392 QTextStream kout;
393 kout.setDevice(&ks);
394 kout << displayName << endl;
395 kout << kickstartType << endl;
396 kout << "# DeviceModel: " << deviceModel << endl;
397 kout << "# DeviceVariant: " << deviceInfo.deviceVariant(true) << endl;
398 kout << "# Brand: " << repoOverride.value("brand") << endl;
399
400 // Repository-specific variables in format "# Var@<repo>@<var>: <value>"
401 for (auto const &repo : deviceInfo.value("repository-specific-variables").toStringList()) {
402 if (!repo.startsWith(deviceModel + '-'))
403 continue;
404
405 QHash<QString, QString> section;
406 deviceInfo.variableSection(repo, &section);
407 for (auto var = section.cbegin(); var != section.cend(); ++var)
408 kout << "# Var@" << repo.mid(deviceModel.size() + 1) << '@' << var.key() << ": " << var.value() << endl;
409 }
410
411 if (!suggestedFeatures.isEmpty())
412 kout << suggestedFeatures << endl;
413 kout << "# SuggestedImageType: " << imageType << endl;
414 kout << "# SuggestedArchitecture: " << imageArchitecture << endl << endl;
415 kout << commands().join("\n") << endl << endl;
416 foreach (const QString &section, commandSections) {
417 kout << commandSection(section).join("\n") << endl << endl;
418 }
419 QString repoSection = repos().join("\n");
420 kout << repoSection << endl << endl;
421 kout << packagesSection("packages").join("\n") << endl << endl;
422 kout << packagesSection("attachment").join("\n") << endl << endl;
423 // TODO: now that extending scriptlet section is might make sense to make it configurable
424 kout << scriptletSection("pre", Chroot).join("\n") << endl << endl;
425 kout << scriptletSection("post", Chroot).join("\n") << endl << endl;
426 kout << scriptletSection("post", NoChroot).join("\n") << endl << endl;
427 kout << scriptletSection("pack", DeviceSpecific).join("\n") << endl << endl;
428 // POST, die-on-error
429
430 return true;
431}
void variableSection(const QString &section, QHash< QString, QString > *storageHash)
QVariant value(const QString &key, const QVariant &value=QVariant())
QVariant variable(const QString &section, const QString &key)
bool contains(const QString &model=QString())
Q_INVOKABLE QString deviceVariant(bool fallback=false)
Q_INVOKABLE QString deviceModel()
@ Chroot
Chroot is not useful, but helps in making the code more readable.
QStringList repos(int filter=Ssu::NoFilter|Ssu::UserBlacklist)
QStringList repoVariables(QHash< QString, QString > *storageHash, bool rnd=false)
static QString resolveString(const QString &pattern, QHash< QString, QString > *variables, int recursionDepth=0)
@ RndMode
Enable RnD mode for device.
Definition ssu.h:69
@ BoardFilter
Only global repositories.
Definition ssu.h:51
#define SSU_DATA_DIR
Directory where all the non-user modifiable data sits.
Definition constants.h:22