ssu
ssukickstarter.cpp
Go to the documentation of this file.
1 
8 #include <QStringList>
9 #include <QRegExp>
10 #include <QDirIterator>
11 
12 #include "ssukickstarter.h"
13 #include "libssu/sandbox_p.h"
14 #include "libssu/ssurepomanager.h"
15 #include "libssu/ssuvariables_p.h"
16 
17 #include "../constants.h"
18 
19 /* TODO:
20  * - commands from the command section should be verified
21  */
22 
23 
24 SsuKickstarter::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 
35 QStringList 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 
55 QStringList 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 
96 QString SsuKickstarter::replaceSpaces(const QString &value)
97 {
98  QString retval = value;
99  return retval.replace(" ", "_");
100 }
101 
102 QStringList 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((rndMode ? repoOverride.value("rndRelease")
126  : repoOverride.value("release")))
127  .arg((rndMode ? "-" + repoOverride.value("flavourName")
128  : QString()))
129  .arg(repoUrl)
130  );
131  } else {
132  result.append(QString("repo --name=%1-%2%3 --baseurl=%4")
133  .arg(repo)
134  .arg((rndMode ? repoOverride.value("rndRelease")
135  : repoOverride.value("release")))
136  .arg((rndMode ? "-" + repoOverride.value("flavourName")
137  : QString()))
138  .arg(repoUrl)
139  );
140  }
141  }
142 
143  return result;
144 }
145 
146 QStringList SsuKickstarter::packagesSection(const QString &name)
147 {
148  QStringList result;
149 
150  if (name == "packages") {
151  // insert @vendor configuration device
152  QString configuration = QString("@%1 Configuration %2")
153  .arg(repoOverride.value("brand"))
154  .arg(deviceModel);
155  result.append(configuration);
156 
157  result.sort();
158  result.removeDuplicates();
159  } else {
160  result = commandSection(name);
161  }
162 
163  result.prepend("%" + name);
164  result.append("%end");
165  return result;
166 }
167 
168 // we intentionally don't support device-specific post scriptlets
169 QStringList SsuKickstarter::scriptletSection(const QString &name, int flags)
170 {
171  QStringList result;
172  QString path;
173  QDir dir;
174 
175  if ((flags & NoChroot) == NoChroot)
176  path = Sandbox::map(QString("/%1/kickstart/%2_nochroot/")
177  .arg(SSU_DATA_DIR)
178  .arg(name));
179  else
180  path = Sandbox::map(QString("/%1/kickstart/%2/")
181  .arg(SSU_DATA_DIR)
182  .arg(name));
183 
184  if ((flags & DeviceSpecific) == DeviceSpecific) {
185  if (dir.exists(path + "/" + replaceSpaces(deviceModel.toLower())))
186  path = path + "/" + replaceSpaces(deviceModel.toLower());
187  else
188  path = path + "/default";
189  }
190 
191  dir.setPath(path);
192  QStringList scriptlets = dir.entryList(QDir::AllEntries | QDir::NoDot | QDir::NoDotDot,
193  QDir::Name);
194 
195  foreach (const QString &scriptlet, scriptlets) {
196  QFile file(dir.filePath(scriptlet));
197  result.append("### begin " + scriptlet);
198  if (file.open(QIODevice::ReadOnly | QIODevice::Text)) {
199  QTextStream in(&file);
200  while (!in.atEnd())
201  result.append(in.readLine());
202  }
203  result.append("### end " + scriptlet);
204  }
205 
206  if (!result.isEmpty()) {
207  result.prepend(QString("export SSU_RELEASE_TYPE=%1")
208  .arg(rndMode ? "rnd" : "release"));
209 
210  if ((flags & NoChroot) == NoChroot)
211  result.prepend("%" + name + " --nochroot --erroronfail");
212  else
213  result.prepend("%" + name + " --erroronfail");
214 
215  result.append("%end");
216  }
217 
218  return result;
219 }
220 
221 void SsuKickstarter::setRepoParameters(QHash<QString, QString> parameters)
222 {
223  repoOverride = parameters;
224 
225  if (repoOverride.contains("model"))
226  deviceModel = repoOverride.value("model");
227 }
228 
229 bool SsuKickstarter::write(const QString &kickstart)
230 {
231  QTextStream qerr(stderr);
232  QStringList commandSections;
233 
234  // initialize with default 'part' for compatibility, as partitions
235  // used to work without configuration. It'll get replaced with
236  // configuration values, if found
237  commandSections.append("part");
238 
239  // rnd mode should not come from the defaults
240  if (repoOverride.contains("rnd")) {
241  if (repoOverride.value("rnd") == "true")
242  rndMode = true;
243  else if (repoOverride.value("rnd") == "false")
244  rndMode = false;
245  }
246 
247  if (repoOverride.contains("domain")) {
248  ssu.setDomain(repoOverride.value("domain"));
249  }
250 
251  QHash<QString, QString> defaults;
252  // get generic repo variables; adaptation specific bits are not interesting in the kickstart
253  SsuRepoManager repoManager;
254  repoManager.repoVariables(&defaults, rndMode);
255 
256  // overwrite with kickstart defaults
257  SsuDeviceInfo deviceInfo(deviceModel);
258  deviceInfo.variableSection("kickstart-defaults", &defaults);
259  if (deviceInfo.variable("kickstart-defaults", "commandSections")
260  .canConvert(QMetaType::QStringList)) {
261  commandSections =
262  deviceInfo.variable("kickstart-defaults", "commandSections").toStringList();
263  }
264 
265  QHash<QString, QString>::const_iterator it = defaults.constBegin();
266  while (it != defaults.constEnd()) {
267  if (!repoOverride.contains(it.key()))
268  repoOverride.insert(it.key(), it.value());
269  it++;
270  }
271 
272  // in rnd mode both rndRelease an release should be the same,
273  // as the variable name used is %(release)
274  if (rndMode && repoOverride.contains("rndRelease"))
275  repoOverride.insert("release", repoOverride.value("rndRelease"));
276 
277  // release mode variables should not contain flavourName
278  if (!rndMode && repoOverride.contains("flavourName"))
279  repoOverride.remove("flavourName");
280 
281  //TODO: check for mandatory keys, ..
282  if (!repoOverride.contains("deviceModel"))
283  repoOverride.insert("deviceModel", deviceInfo.deviceModel());
284 
285  // do sanity checking on the model
286  if (deviceInfo.contains() == false) {
287  qerr << "Device model '" << deviceInfo.deviceModel() << "' does not exist" << endl;
288 
289  if (repoOverride.value("force") != "true")
290  return false;
291  }
292 
293  QRegExp regex(" {2,}", Qt::CaseSensitive, QRegExp::RegExp2);
294  if (regex.indexIn(deviceInfo.deviceModel(), 0) != -1) {
295  qerr << "Device model '" << deviceInfo.deviceModel()
296  << "' contains multiple consecutive spaces." << endl;
297  if (deviceInfo.contains())
298  qerr << "Since the model exists it looks like your configuration is broken." << endl;
299  return false;
300  }
301 
302  if (!repoOverride.contains("brand")) {
303  qerr << "Brand is missing in your configuration." << endl;
304  return false;
305  }
306 
307  bool opened = false;
308  QString outputDir = repoOverride.value("outputdir");
309  if (!outputDir.isEmpty()) outputDir.append("/");
310 
311  QFile ks;
312 
313  if (kickstart.isEmpty()) {
314  SsuVariables var;
315 
316  if (repoOverride.contains("filename")) {
317  QString fileName = QString("%1%2")
318  .arg(outputDir)
319  .arg(replaceSpaces(var.resolveString(repoOverride.value("filename"),
320  &repoOverride)));
321 
322  ks.setFileName(fileName);
323  opened = ks.open(QIODevice::WriteOnly);
324  } else {
325  qerr << "No filename specified, and no default filename configured" << endl;
326  return false;
327  }
328  } else if (kickstart == "-") {
329  opened = ks.open(stdout, QIODevice::WriteOnly);
330  } else {
331  ks.setFileName(outputDir + kickstart);
332  opened = ks.open(QIODevice::WriteOnly);
333  }
334 
335  if (!opened) {
336  qerr << "Unable to write output file " << ks.fileName() << ": " << ks.errorString() << endl;
337  return false;
338  } else if (!ks.fileName().isEmpty()) {
339  qerr << "Writing kickstart to " << ks.fileName() << endl;
340  }
341 
342  QString displayName = QString("# DisplayName: %1 %2/%3 (%4) %5")
343  .arg(repoOverride.value("brand"))
344  .arg(deviceInfo.deviceModel())
345  .arg(repoOverride.value("arch"))
346  .arg((rndMode ? "rnd"
347  : "release"))
348  .arg(repoOverride.value("version"));
349 
350  // Feature names can be prefixed with '-' to inhibit implicit suggestion
351  QStringList featuresList = deviceInfo.value("img-features").toStringList();
352 
353  // Add developer-mode feature to rnd images by default
354  if (rndMode && !featuresList.contains("-developer-mode"))
355  featuresList << "developer-mode";
356 
357  featuresList = featuresList.filter(QRegExp("^[^-]"));
358 
359  QString suggestedFeatures;
360 
361  // work around some idiotic JS list parsing on our side by terminating one-element list by comma
362  if (featuresList.count() == 1)
363  suggestedFeatures = QString("# SuggestedFeatures: %1,")
364  .arg(featuresList.join(", "));
365  else if (featuresList.count() > 1)
366  suggestedFeatures = QString("# SuggestedFeatures: %1")
367  .arg(featuresList.join(", "));
368 
369  QString imageType = "fs";
370  if (!deviceInfo.value("img-type").toString().isEmpty())
371  imageType = deviceInfo.value("img-type").toString();
372 
373  QString imageArchitecture = "armv7hl";
374  if (!deviceInfo.value("img-arch").toString().isEmpty())
375  imageArchitecture = deviceInfo.value("img-arch").toString();
376 
377  QString kickstartType = QString("# KickstartType: %1")
378  .arg((rndMode ? "rnd" : "release"));
379 
380  const QString deviceModel = deviceInfo.deviceModel();
381 
382  QTextStream kout;
383  kout.setDevice(&ks);
384  kout << displayName << endl;
385  kout << kickstartType << endl;
386  kout << "# DeviceModel: " << deviceModel << endl;
387  kout << "# DeviceVariant: " << deviceInfo.deviceVariant(true) << endl;
388  kout << "# Brand: " << repoOverride.value("brand") << endl;
389 
390  // Repository-specific variables in format "# Var@<repo>@<var>: <value>"
391  for (auto const &repo : deviceInfo.value("repository-specific-variables").toStringList()) {
392  if (!repo.startsWith(deviceModel + '-'))
393  continue;
394 
395  QHash<QString, QString> section;
396  deviceInfo.variableSection(repo, &section);
397  for (auto var = section.cbegin(); var != section.cend(); ++var)
398  kout << "# Var@" << repo.mid(deviceModel.size() + 1) << '@' << var.key() << ": " << var.value() << endl;
399  }
400 
401  if (!suggestedFeatures.isEmpty())
402  kout << suggestedFeatures << endl;
403  kout << "# SuggestedImageType: " << imageType << endl;
404  kout << "# SuggestedArchitecture: " << imageArchitecture << endl << endl;
405  kout << commands().join("\n") << endl << endl;
406  foreach (const QString &section, commandSections) {
407  kout << commandSection(section).join("\n") << endl << endl;
408  }
409 
410  // this allows simple search and replace postprocessing of the repos section
411  // to overcome shortcomings of the "keep image creation simple token based"
412  // approach
413  // TODO: QHash looks messy in the config, provide tool to edit it
414  QString repoSection = repos().join("\n");
415  if (deviceInfo.variable("kickstart-defaults", "urlPostprocess")
416  .canConvert(QMetaType::QVariantHash)) {
417  QHash<QString, QVariant> postproc =
418  deviceInfo.variable("kickstart-defaults", "urlPostprocess").toHash();
419 
420  QHash<QString, QVariant>::const_iterator it = postproc.constBegin();
421  while (it != postproc.constEnd()) {
422  QRegExp regex(it.key(), Qt::CaseSensitive, QRegExp::RegExp2);
423 
424  repoSection.replace(regex, it.value().toString());
425  it++;
426  }
427  }
428 
429  kout << repoSection << endl << endl;
430  kout << packagesSection("packages").join("\n") << endl << endl;
431  kout << packagesSection("attachment").join("\n") << endl << endl;
432  // TODO: now that extending scriptlet section is might make sense to make it configurable
433  kout << scriptletSection("pre", Chroot).join("\n") << endl << endl;
434  kout << scriptletSection("post", Chroot).join("\n") << endl << endl;
435  kout << scriptletSection("post", NoChroot).join("\n") << endl << endl;
436  kout << scriptletSection("pack", DeviceSpecific).join("\n") << endl << endl;
437  // POST, die-on-error
438 
439  return true;
440 }
SsuDeviceInfo::contains
bool contains(const QString &model=QString())
Definition: ssudeviceinfo.cpp:128
SsuKickstarter::Chroot
@ Chroot
Chroot is not useful, but helps in making the code more readable.
Definition: ssukickstarter.h:27
ssuvariables_p.h
Ssu::RndMode
@ RndMode
Enable RnD mode for device.
Definition: ssu.h:68
SsuRepoManager
Definition: ssurepomanager.h:18
SsuDeviceInfo::variable
QVariant variable(const QString &section, const QString &key)
Definition: ssudeviceinfo.cpp:455
Ssu::setDomain
Q_INVOKABLE void setDomain(const QString &domain)
See SsuCoreConfig::setDomain.
Definition: ssu.cpp:215
ssurepomanager.h
SsuDeviceInfo::variableSection
void variableSection(const QString &section, QHash< QString, QString > *storageHash)
Definition: ssudeviceinfo.cpp:466
ssukickstarter.h
SsuRepoManager::repoVariables
QStringList repoVariables(QHash< QString, QString > *storageHash, bool rnd=false)
Definition: ssurepomanager.cpp:377
Ssu::repoUrl
QString repoUrl(const QString &repoName, bool rndRepo=false, QHash< QString, QString > repoParameters=QHash< QString, QString >(), QHash< QString, QString > parametersOverride=QHash< QString, QString >())
Definition: ssu.cpp:312
SsuRepoManager::repos
QStringList repos(int filter=Ssu::NoFilter)
Definition: ssurepomanager.cpp:169
sandbox_p.h
SSU_DATA_DIR
#define SSU_DATA_DIR
Directory where all the non-user modifiable data sits.
Definition: constants.h:22
SsuDeviceInfo
Definition: ssudeviceinfo.h:17
Ssu::BoardFilter
@ BoardFilter
Only global repositories, with user blacklist ignored.
Definition: ssu.h:52
SsuVariables
Definition: ssuvariables_p.h:16
SsuDeviceInfo::deviceModel
Q_INVOKABLE QString deviceModel()
Definition: ssudeviceinfo.cpp:185
SsuDeviceInfo::deviceVariant
Q_INVOKABLE QString deviceVariant(bool fallback=false)
Definition: ssudeviceinfo.cpp:170
SsuDeviceInfo::value
QVariant value(const QString &key, const QVariant &value=QVariant())
Definition: ssudeviceinfo.cpp:486
SsuVariables::resolveString
static QString resolveString(const QString &pattern, QHash< QString, QString > *variables, int recursionDepth=0)
Definition: ssuvariables.cpp:39