ssukickstarter.cpp 10.4 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12
/**
 * @file ssukickstarter.cpp
 * @copyright 2013 Jolla Ltd.
 * @author Bernd Wachter <bwachter@lart.info>
 * @date 2013
 */

#include <QStringList>
#include <QRegExp>
#include <QDirIterator>

#include "ssukickstarter.h"
13
#include "libssu/sandbox_p.h"
14 15
#include "libssu/ssurepomanager.h"
#include "libssu/ssuvariables.h"
16 17 18 19 20 21 22 23 24

#include "../constants.h"

/* TODO:
 * - commands from the command section should be verified
 * - allow overriding brand key
 */


25
SsuKickstarter::SsuKickstarter() {
26 27 28 29 30 31 32 33 34 35 36 37 38
  SsuDeviceInfo deviceInfo;
  deviceModel = deviceInfo.deviceModel();

  if ((ssu.deviceMode() & Ssu::RndMode) == Ssu::RndMode)
    rndMode = true;
  else
    rndMode = false;
}

QStringList SsuKickstarter::commands(){
  SsuDeviceInfo deviceInfo(deviceModel);
  QStringList result;

39 40 41
  QHash<QString, QString> h;

  deviceInfo.variableSection("kickstart-commands", &h);
42 43 44 45 46 47 48 49 50 51 52 53

  // read commands from variable, ...

  QHash<QString, QString>::const_iterator it = h.constBegin();
  while (it != h.constEnd()){
    result.append(it.key() + " " + it.value());
    it++;
  }

  return result;
}

54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98
QStringList SsuKickstarter::commandSection(const QString &section, const QString &description){
  QStringList result;
  SsuDeviceInfo deviceInfo(deviceModel);
  QString commandFile;
  QFile part;

  QDir dir(Sandbox::map(QString("/%1/kickstart/%2/")
                        .arg(SSU_DATA_DIR)
                        .arg(section)));

  if (dir.exists(replaceSpaces(deviceModel.toLower())))
    commandFile = replaceSpaces(deviceModel.toLower());
  else if (dir.exists(replaceSpaces(deviceInfo.deviceVariant(true).toLower())))
    commandFile = replaceSpaces(deviceInfo.deviceVariant(true).toLower());
  else if (dir.exists("default"))
    commandFile = "default";
  else {
    if (description.isEmpty())
      result.append("## No suitable configuration found in " + dir.path());
    else
      result.append("## No configuration for " + description + " found.");
    return result;
  }

  QFile file(dir.path() + "/" + commandFile);

  if (description.isEmpty())
    result.append("### Commands from " + dir.path() + "/" + commandFile);
  else
    result.append("### " + description + " from " + commandFile);

  if (file.open(QIODevice::ReadOnly | QIODevice::Text)){
    QTextStream in(&file);
    while (!in.atEnd())
      result.append(in.readLine());
  }

  return result;
}

QString SsuKickstarter::replaceSpaces(const QString &value){
  QString retval = value;
  return retval.replace(" ", "_");
}

99 100 101 102
QStringList SsuKickstarter::repos(){
  QStringList result;
  SsuDeviceInfo deviceInfo(deviceModel);

103
  QStringList repos = deviceInfo.repos(rndMode, SsuRepoManager::BoardFilter);
104 105 106

  foreach (const QString &repo, repos){
    QString repoUrl = ssu.repoUrl(repo, rndMode, QHash<QString, QString>(), repoOverride);
107 108 109
    // Adaptation repos need to have separate naming so that when images are done
    // the repository caches will not be mixed with each other.
    if (repo.startsWith("adaptation")) {
110 111
      result.append(QString("repo --name=%1-%2-%3 --baseurl=%4")
                    .arg(repo)
112
                    .arg(replaceSpaces(deviceModel))
113 114 115 116
                    .arg((rndMode ? repoOverride.value("rndRelease")
                          : repoOverride.value("release")))
                    .arg(repoUrl)
        );
117
    }
118 119 120 121 122 123 124
    else
      result.append(QString("repo --name=%1-%2 --baseurl=%3")
                    .arg(repo)
                    .arg((rndMode ? repoOverride.value("rndRelease")
                          : repoOverride.value("release")))
                    .arg(repoUrl)
        );
125 126 127 128 129 130 131 132 133 134
  }

  return result;
}

QStringList SsuKickstarter::packages(){
  QStringList result;

  // insert @vendor configuration device
  QString configuration = QString("@%1 Configuration %2")
Aard's avatar
Aard committed
135
    .arg(repoOverride.value("brand"))
136 137 138 139 140 141 142 143 144 145 146
    .arg(deviceModel);
  result.append(configuration);

  result.sort();
  result.removeDuplicates();
  result.prepend("%packages");
  result.append("%end");
  return result;
}

// we intentionally don't support device-specific post scriptlets
147
QStringList SsuKickstarter::scriptletSection(QString name, int flags){
148 149 150 151
  QStringList result;
  QString path;
  QDir dir;

152 153
  if ((flags & NoChroot) == NoChroot)
    path = Sandbox::map(QString("/%1/kickstart/%2_nochroot/")
154
      .arg(SSU_DATA_DIR)
155
      .arg(name));
156
  else
157
    path = Sandbox::map(QString("/%1/kickstart/%2/")
158
      .arg(SSU_DATA_DIR)
159
      .arg(name));
160

161 162 163 164 165 166 167
  if ((flags & DeviceSpecific) == DeviceSpecific){
    if (dir.exists(path + "/" + replaceSpaces(deviceModel.toLower())))
      path = path + "/" + replaceSpaces(deviceModel.toLower());
    else
      path = path + "/default";
  }

168 169 170 171 172
  dir.setPath(path);
  QStringList scriptlets = dir.entryList(QDir::AllEntries|QDir::NoDot|QDir::NoDotDot,
                                         QDir::Name);

  foreach (const QString &scriptlet, scriptlets){
173
    QFile file(dir.filePath(scriptlet));
174 175 176 177 178 179 180 181 182 183
    result.append("### begin " + scriptlet);
    if (file.open(QIODevice::ReadOnly | QIODevice::Text)){
      QTextStream in(&file);
      while (!in.atEnd())
        result.append(in.readLine());
    }
    result.append("### end " + scriptlet);
  }

  if (!result.isEmpty()){
184 185 186
    result.prepend(QString("export SSU_RELEASE_TYPE=%1")
                   .arg(rndMode ? "rnd" : "release"));

187
    if ((flags & NoChroot) == NoChroot)
188
      result.prepend("%" + name + " --nochroot");
189 190 191
    else
      result.prepend("%" + name);

192 193 194 195 196 197 198 199 200 201 202 203 204
    result.append("%end");
  }

  return result;
}

void SsuKickstarter::setRepoParameters(QHash<QString, QString> parameters){
  repoOverride = parameters;

  if (repoOverride.contains("model"))
    deviceModel = repoOverride.value("model");
}

205
bool SsuKickstarter::write(QString kickstart){
206 207
  QFile ks;
  QTextStream kout;
208 209 210 211
  QTextStream qerr(stderr);
  SsuDeviceInfo deviceInfo(deviceModel);
  SsuRepoManager repoManager;
  SsuVariables var;
212 213 214 215 216 217
  QStringList commandSections;

  // initialize with default 'part' for compatibility, as partitions
  // used to work without configuration. It'll get replaced with
  // configuration values, if found
  commandSections.append("part");
218

219 220 221 222 223 224
  // rnd mode should not come from the defaults
  if (repoOverride.contains("rnd")){
    if (repoOverride.value("rnd") == "true")
      rndMode = true;
    else if (repoOverride.value("rnd") == "false")
      rndMode = false;
225 226
  }

227 228 229 230 231 232 233
  QHash<QString, QString> defaults;
  // get generic repo variables; domain and adaptation specific bits are not interesting
  // in the kickstart
  repoManager.repoVariables(&defaults, rndMode);

  // overwrite with kickstart defaults
  deviceInfo.variableSection("kickstart-defaults", &defaults);
234 235 236 237 238
  if (deviceInfo.variable("kickstart-defaults", "commandSections")
      .canConvert(QMetaType::QStringList)){
    commandSections =
      deviceInfo.variable("kickstart-defaults", "commandSections").toStringList();
  }
239 240 241 242 243 244 245 246

  QHash<QString, QString>::const_iterator it = defaults.constBegin();
  while (it != defaults.constEnd()){
    if (!repoOverride.contains(it.key()))
      repoOverride.insert(it.key(), it.value());
    it++;
  }

247 248 249 250 251
  // in rnd mode both rndRelease an release should be the same,
  // as the variable name used is %(release)
  if (rndMode && repoOverride.contains("rndRelease"))
    repoOverride.insert("release", repoOverride.value("rndRelease"));

252 253 254 255 256 257 258 259
  // release mode variables should not contain flavourName
  if (!rndMode && repoOverride.contains("flavourName"))
    repoOverride.remove("flavourName");

  //TODO: check for mandatory keys, brand, ..
  if (!repoOverride.contains("deviceModel"))
    repoOverride.insert("deviceModel", deviceInfo.deviceModel());

260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276
  // do sanity checking on the model
  if (deviceInfo.contains() == false) {
    qerr << "Device model '" << deviceInfo.deviceModel() << "' does not exist" << endl;

    if (repoOverride.value("force") != "true")
      return false;
  }

  QRegExp regex(" {2,}", Qt::CaseSensitive, QRegExp::RegExp2);
  if (regex.indexIn(deviceInfo.deviceModel(), 0) != -1){
    qerr << "Device model '" << deviceInfo.deviceModel()
         << "' contains multiple consecutive spaces." << endl;
    if (deviceInfo.contains())
      qerr << "Since the model exists it looks like your configuration is broken." << endl;
    return false;
  }

Aard's avatar
Aard committed
277 278 279 280 281
  if (!repoOverride.contains("brand")){
    qerr << "No brand set. Check your configuration." << endl;
    return false;
  }

282 283 284 285
  bool opened = false;
  QString outputDir = repoOverride.value("outputdir");
  if (!outputDir.isEmpty()) outputDir.append("/");

286 287
  if (kickstart.isEmpty()){
    if (repoOverride.contains("filename")){
288
      QString fileName = QString("%1%2")
289 290 291
        .arg(outputDir)
        .arg(replaceSpaces(var.resolveString(repoOverride.value("filename"),
                                             &repoOverride)));
292

Aard's avatar
Aard committed
293
      ks.setFileName(fileName);
294
      opened = ks.open(QIODevice::WriteOnly);
295 296 297 298 299
    } else {
      qerr << "No filename specified, and no default filename configured" << endl;
      return false;
    }
  } else if (kickstart == "-")
300
    opened = ks.open(stdout, QIODevice::WriteOnly);
301
  else {
302 303 304 305 306 307 308
    ks.setFileName(outputDir + kickstart);
    opened = ks.open(QIODevice::WriteOnly);
  }

  if (!opened) {
    qerr << "Unable to write output file " << ks.fileName() << ": " << ks.errorString() << endl;
    return false;
309 310
  } else if (!ks.fileName().isEmpty())
    qerr << "Writing kickstart to " << ks.fileName() << endl;
311

Aard's avatar
Aard committed
312 313 314 315 316 317 318 319
  QString displayName = QString("# DisplayName: %1 %2/%3 (%4) %5")
                                .arg(repoOverride.value("brand"))
                                .arg(deviceInfo.deviceModel())
                                .arg(repoOverride.value("arch"))
                                .arg((rndMode ? "rnd"
                                              : "release"))
                                .arg(repoOverride.value("version"));

320
  kout.setDevice(&ks);
Aard's avatar
Aard committed
321
  kout << displayName << endl << endl;
322
  kout << commands().join("\n") << endl << endl;
323 324 325
  foreach (const QString &section, commandSections){
    kout << commandSection(section).join("\n") << endl << endl;
  }
326 327
  kout << repos().join("\n") << endl << endl;
  kout << packages().join("\n") << endl << endl;
328 329 330 331 332
  // TODO: now that extending scriptlet section is might make sense to make it configurable
  kout << scriptletSection("pre", Chroot).join("\n") << endl << endl;
  kout << scriptletSection("post", Chroot).join("\n") << endl << endl;
  kout << scriptletSection("post", NoChroot).join("\n") << endl << endl;
  kout << scriptletSection("pack", DeviceSpecific).join("\n") << endl << endl;
333
  // POST, die-on-error
334 335

  return true;
336
}