LCOV - code coverage report
Current view: top level - tools/packager - packager.cpp (source / functions) Coverage Total Hit
Test: QtApplicationManager Lines: 0.0 % 199 0
Test Date: 2024-11-23 10:23:45 Functions: 0.0 % 3 0
Legend: Lines: hit not hit | Branches: + taken - not taken # not executed Branches: 0.0 % 445 0

             Branch data     Line data    Source code
       1                 :             : // Copyright (C) 2021 The Qt Company Ltd.
       2                 :             : // Copyright (C) 2019 Luxoft Sweden AB
       3                 :             : // Copyright (C) 2018 Pelagicore AG
       4                 :             : // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
       5                 :             : 
       6                 :             : #include <memory>
       7                 :             : #include <stdio.h>
       8                 :             : 
       9                 :             : #include <QCoreApplication>
      10                 :             : #include <QCommandLineParser>
      11                 :             : #include <QStringList>
      12                 :             : #include <QFile>
      13                 :             : #include <QJsonArray>
      14                 :             : #include <QJsonDocument>
      15                 :             : #include <QDebug>
      16                 :             : 
      17                 :             : #include <QtAppManCommon/exception.h>
      18                 :             : #include <QtAppManCommon/qtyaml.h>
      19                 :             : #include <QtAppManCommon/utilities.h>
      20                 :             : #include <QtAppManCrypto/cryptography.h>
      21                 :             : #include <QtAppManPackage/packageutilities.h>
      22                 :             : #include "packagingjob.h"
      23                 :             : 
      24                 :             : using namespace Qt::StringLiterals;
      25                 :             : 
      26                 :             : 
      27                 :             : QT_USE_NAMESPACE_AM
      28                 :             : 
      29                 :             : enum Command {
      30                 :             :     NoCommand,
      31                 :             :     CreatePackage,
      32                 :             :     DevSignPackage,
      33                 :             :     DevVerifyPackage,
      34                 :             :     StoreSignPackage,
      35                 :             :     StoreVerifyPackage,
      36                 :             :     YamlToJson
      37                 :             : };
      38                 :             : 
      39                 :             : // REMEMBER to update the completion file util/bash/appman-prompt, if you apply changes below!
      40                 :             : static struct {
      41                 :             :     Command command;
      42                 :             :     const char *name;
      43                 :             :     const char *description;
      44                 :             : } commandTable[] = {
      45                 :             :     { CreatePackage,      "create-package",       "Create a new package." },
      46                 :             :     { DevSignPackage,     "dev-sign-package",     "Add developer signature to package." },
      47                 :             :     { DevVerifyPackage,   "dev-verify-package",   "Verify developer signature on package." },
      48                 :             :     { StoreSignPackage,   "store-sign-package",   "Add store signature to package." },
      49                 :             :     { StoreVerifyPackage, "store-verify-package", "Verify store signature on package." },
      50                 :             :     { YamlToJson,         "yaml-to-json",         "Convenience functionality for build systems (internal)." }
      51                 :             : };
      52                 :             : 
      53                 :           0 : static Command command(QCommandLineParser &clp)
      54                 :             : {
      55         [ #  # ]:           0 :     if (!clp.positionalArguments().isEmpty()) {
      56         [ #  # ]:           0 :         QByteArray cmd = clp.positionalArguments().at(0).toLatin1();
      57                 :             : 
      58         [ #  # ]:           0 :         for (uint i = 0; i < sizeof(commandTable) / sizeof(commandTable[0]); ++i) {
      59         [ #  # ]:           0 :             if (cmd == commandTable[i].name) {
      60         [ #  # ]:           0 :                 clp.clearPositionalArguments();
      61   [ #  #  #  # ]:           0 :                 clp.addPositionalArgument(QString::fromLatin1(cmd),
      62         [ #  # ]:           0 :                                           QString::fromLatin1(commandTable[i].description),
      63         [ #  # ]:           0 :                                           QString::fromLatin1(cmd));
      64                 :           0 :                 return commandTable[i].command;
      65                 :             :             }
      66                 :             :         }
      67                 :           0 :     }
      68                 :             :     return NoCommand;
      69                 :             : }
      70                 :             : 
      71                 :           0 : int main(int argc, char *argv[])
      72                 :             : {
      73                 :             :     // enable OpenSSL3 to load old certificates
      74                 :           0 :     Cryptography::enableOpenSsl3LegacyProvider();
      75                 :             : 
      76         [ #  # ]:           0 :     QCoreApplication::setApplicationName(u"Qt ApplicationManager Packager"_s);
      77         [ #  # ]:           0 :     QCoreApplication::setOrganizationName(u"QtProject"_s);
      78         [ #  # ]:           0 :     QCoreApplication::setOrganizationDomain(u"qt-project.org"_s);
      79         [ #  # ]:           0 :     QCoreApplication::setApplicationVersion(QString::fromLatin1(QT_AM_VERSION_STR));
      80                 :             : 
      81                 :           0 :     QCoreApplication a(argc, argv);
      82                 :             : 
      83         [ #  # ]:           0 :     QByteArray desc = "\n\nAvailable commands are:\n";
      84                 :           0 :     size_t longestName = 0;
      85         [ #  # ]:           0 :     for (uint i = 0; i < sizeof(commandTable) / sizeof(commandTable[0]); ++i)
      86   [ #  #  #  # ]:           0 :         longestName = qMax(longestName, qstrlen(commandTable[i].name));
      87         [ #  # ]:           0 :     for (uint i = 0; i < sizeof(commandTable) / sizeof(commandTable[0]); ++i) {
      88         [ #  # ]:           0 :         desc += "  ";
      89         [ #  # ]:           0 :         desc += commandTable[i].name;
      90   [ #  #  #  # ]:           0 :         desc += QByteArray(1 + qsizetype(longestName - qstrlen(commandTable[i].name)), ' ');
      91         [ #  # ]:           0 :         desc += commandTable[i].description;
      92         [ #  # ]:           0 :         desc += '\n';
      93                 :             :     }
      94                 :             : 
      95         [ #  # ]:           0 :     desc += "\nMore information about each command can be obtained by running\n" \
      96                 :           0 :             "  appman-packager <command> --help";
      97                 :             : 
      98         [ #  # ]:           0 :     QCommandLineParser clp;
      99   [ #  #  #  #  :           0 :     clp.setApplicationDescription(u"\n"_s + QCoreApplication::applicationName() + QString::fromLatin1(desc));
             #  #  #  # ]
     100                 :             : 
     101         [ #  # ]:           0 :     clp.addHelpOption();
     102         [ #  # ]:           0 :     clp.addVersionOption();
     103                 :             : 
     104         [ #  # ]:           0 :     clp.addPositionalArgument(u"command"_s, u"The command to execute."_s);
     105                 :             : 
     106                 :             :     // ignore unknown options for now -- the sub-commands may need them later
     107         [ #  # ]:           0 :     clp.setOptionsAfterPositionalArgumentsMode(QCommandLineParser::ParseAsPositionalArguments);
     108                 :             : 
     109   [ #  #  #  #  :           0 :     if (!clp.parse(QCoreApplication::arguments())) {
                   #  # ]
     110   [ #  #  #  #  :           0 :         fprintf(stderr, "%s\n", qPrintable(clp.errorText()));
                   #  # ]
     111                 :           0 :         exit(1);
     112                 :             :     }
     113         [ #  # ]:           0 :     clp.setOptionsAfterPositionalArgumentsMode(QCommandLineParser::ParseAsOptions);
     114                 :             : 
     115                 :           0 :     try {
     116                 :           0 :         std::unique_ptr<PackagingJob> p;
     117                 :             : 
     118                 :             :         // REMEMBER to update the completion file util/bash/appman-prompt, if you apply changes below!
     119   [ #  #  #  #  :           0 :         switch (command(clp)) {
             #  #  #  #  
                      # ]
     120                 :           0 :         default:
     121                 :           0 :         case NoCommand:
     122   [ #  #  #  # ]:           0 :             if (clp.isSet(u"version"_s))
     123                 :           0 :                 clp.showVersion();
     124   [ #  #  #  # ]:           0 :             if (clp.isSet(u"help"_s))
     125                 :           0 :                 clp.showHelp();
     126                 :           0 :             clp.showHelp(1);
     127                 :           0 :             break;
     128                 :             : 
     129                 :           0 :         case CreatePackage: {
     130   [ #  #  #  # ]:           0 :             clp.addOption({ u"verbose"_s, u"Dump the package's meta-data header and footer information to stdout."_s });
     131   [ #  #  #  # ]:           0 :             clp.addOption({ u"json"_s,    u"Output in JSON format instead of YAML."_s });
     132   [ #  #  #  #  :           0 :             clp.addOption({{ u"extra-metadata"_s,      u"m"_s }, u"Add extra meta-data to the package, supplied on the command line."_s, u"yaml-snippet"_s });
             #  #  #  # ]
     133   [ #  #  #  #  :           0 :             clp.addOption({{ u"extra-metadata-file"_s, u"M"_s }, u"Add extra meta-data to the package, read from file."_s, u"yaml-file"_s });
             #  #  #  # ]
     134   [ #  #  #  #  :           0 :             clp.addOption({{ u"extra-signed-metadata"_s,      u"s"_s }, u"Add extra, digitally signed, meta-data to the package, supplied on the command line."_s, u"yaml-snippet"_s });
             #  #  #  # ]
     135   [ #  #  #  #  :           0 :             clp.addOption({{ u"extra-signed-metadata-file"_s, u"S"_s }, u"Add extra, digitally signed, meta-data to the package, read from file."_s, u"yaml-file"_s });
             #  #  #  # ]
     136         [ #  # ]:           0 :             clp.addPositionalArgument(u"package"_s,          u"The file name of the created package."_s);
     137         [ #  # ]:           0 :             clp.addPositionalArgument(u"source-directory"_s, u"The package's content root directory."_s);
     138         [ #  # ]:           0 :             clp.process(a);
     139                 :             : 
     140   [ #  #  #  # ]:           0 :             if (clp.positionalArguments().size() != 3)
     141                 :           0 :                 clp.showHelp(1);
     142                 :             : 
     143                 :           0 :             auto parseYamlMetada = [](const QStringList &metadataSnippets, const QStringList &metadataFiles, bool isSigned) -> QVariantMap {
     144                 :           0 :                 QVariantMap result;
     145                 :           0 :                 QVector<QPair<QByteArray, QString>> metadata;
     146                 :             : 
     147         [ #  # ]:           0 :                 for (const QString &file : metadataFiles) {
     148         [ #  # ]:           0 :                     QFile f(file);
     149   [ #  #  #  # ]:           0 :                     if (!f.open(QIODevice::ReadOnly))
     150                 :           0 :                         throw Exception(f, "Could not open metadata file for reading");
     151   [ #  #  #  # ]:           0 :                     metadata.append(qMakePair(f.readAll(), file));
     152                 :           0 :                 }
     153         [ #  # ]:           0 :                 for (const QString &snippet : metadataSnippets)
     154   [ #  #  #  # ]:           0 :                     metadata.append(qMakePair(snippet.toUtf8(), QString()));
     155                 :             : 
     156   [ #  #  #  #  :           0 :                 for (const auto &md : metadata) {
                   #  # ]
     157                 :           0 :                     QVector<QVariant> docs;
     158                 :           0 :                     try {
     159         [ #  # ]:           0 :                         docs = YamlParser::parseAllDocuments(md.first);
     160         [ -  - ]:           0 :                     } catch (const Exception &e) {
     161                 :           0 :                         throw Exception(Error::IO, "in --extra-%1metadata%2 %3: %4")
     162   [ -  -  -  - ]:           0 :                                 .arg(isSigned ? "signed-" : "").arg(md.second.isEmpty() ? "": "-file")
     163         [ -  - ]:           0 :                                 .arg(md.second.isEmpty() ? u"option"_s : md.second)
     164                 :           0 :                                 .arg(e.errorString());
     165                 :           0 :                     }
     166         [ #  # ]:           0 :                     if (docs.size() < 1) {
     167                 :           0 :                         throw Exception("Could not parse --extra-%1metadata%2 %3: Invalid document format")
     168   [ #  #  #  # ]:           0 :                                 .arg(isSigned ? "signed-" : "").arg(md.second.isEmpty() ? "": "-file")
     169         [ #  # ]:           0 :                                 .arg(md.second.isEmpty() ? u"option"_s : md.second);
     170                 :             :                     }
     171   [ #  #  #  #  :           0 :                     for (const auto &doc : docs)
                   #  # ]
     172   [ #  #  #  # ]:           0 :                         recursiveMergeVariantMap(result, doc.toMap());
     173                 :           0 :                 }
     174                 :           0 :                 return result;
     175                 :           0 :             };
     176                 :             : 
     177         [ #  # ]:           0 :             QVariantMap extraMetaDataMap = parseYamlMetada(clp.values(u"extra-metadata"_s),
     178         [ #  # ]:           0 :                                                            clp.values(u"extra-metadata-file"_s),
     179         [ #  # ]:           0 :                                                            false);
     180         [ #  # ]:           0 :             QVariantMap extraSignedMetaDataMap = parseYamlMetada(clp.values(u"extra-signed-metadata"_s),
     181         [ #  # ]:           0 :                                                                  clp.values(u"extra-signed-metadata-file"_s),
     182         [ #  # ]:           0 :                                                                  true);
     183                 :             : 
     184   [ #  #  #  # ]:           0 :             p.reset(PackagingJob::create(clp.positionalArguments().at(1),
     185   [ #  #  #  # ]:           0 :                                          clp.positionalArguments().at(2),
     186                 :             :                                          extraMetaDataMap,
     187                 :             :                                          extraSignedMetaDataMap,
     188         [ #  # ]:           0 :                                          clp.isSet(u"json"_s)));
     189                 :           0 :             break;
     190                 :           0 :         }
     191                 :           0 :         case DevSignPackage:
     192   [ #  #  #  # ]:           0 :             clp.addOption({ u"verbose"_s, u"Dump the package's meta-data header and footer information to stdout."_s });
     193   [ #  #  #  # ]:           0 :             clp.addOption({ u"json"_s,    u"Output in JSON format instead of YAML."_s });
     194         [ #  # ]:           0 :             clp.addPositionalArgument(u"package"_s,        u"File name of the unsigned package (input)."_s);
     195         [ #  # ]:           0 :             clp.addPositionalArgument(u"signed-package"_s, u"File name of the signed package (output)."_s);
     196         [ #  # ]:           0 :             clp.addPositionalArgument(u"certificate"_s,    u"PKCS#12 certificate file."_s);
     197         [ #  # ]:           0 :             clp.addPositionalArgument(u"password"_s,       u"Password for the PKCS#12 certificate."_s);
     198         [ #  # ]:           0 :             clp.process(a);
     199                 :             : 
     200   [ #  #  #  # ]:           0 :             if (clp.positionalArguments().size() != 5)
     201                 :           0 :                 clp.showHelp(1);
     202                 :             : 
     203   [ #  #  #  # ]:           0 :             p.reset(PackagingJob::developerSign(clp.positionalArguments().at(1),
     204         [ #  # ]:           0 :                                                 clp.positionalArguments().at(2),
     205         [ #  # ]:           0 :                                                 clp.positionalArguments().at(3),
     206   [ #  #  #  # ]:           0 :                                                 clp.positionalArguments().at(4),
     207         [ #  # ]:           0 :                                                 clp.isSet(u"json"_s)));
     208                 :           0 :             break;
     209                 :             : 
     210                 :           0 :         case DevVerifyPackage:
     211   [ #  #  #  # ]:           0 :             clp.addOption({ u"verbose"_s, u"Print details regarding the verification to stdout."_s });
     212         [ #  # ]:           0 :             clp.addPositionalArgument(u"package"_s,      u"File name of the signed package (input)."_s);
     213         [ #  # ]:           0 :             clp.addPositionalArgument(u"certificates"_s, u"The developer's CA certificate file(s)."_s, u"certificates..."_s);
     214         [ #  # ]:           0 :             clp.process(a);
     215                 :             : 
     216   [ #  #  #  # ]:           0 :             if (clp.positionalArguments().size() < 3)
     217                 :           0 :                 clp.showHelp(1);
     218                 :             : 
     219   [ #  #  #  #  :           0 :             p.reset(PackagingJob::developerVerify(clp.positionalArguments().at(1),
                   #  # ]
     220         [ #  # ]:           0 :                                                   clp.positionalArguments().mid(2)));
     221                 :           0 :             break;
     222                 :             : 
     223                 :           0 :         case StoreSignPackage:
     224   [ #  #  #  # ]:           0 :             clp.addOption({ u"verbose"_s, u"Dump the package's meta-data header and footer information to stdout."_s });
     225   [ #  #  #  # ]:           0 :             clp.addOption({ u"json"_s,    u"Output in JSON format instead of YAML."_s });
     226         [ #  # ]:           0 :             clp.addPositionalArgument(u"package"_s,        u"File name of the unsigned package (input)."_s);
     227         [ #  # ]:           0 :             clp.addPositionalArgument(u"signed-package"_s, u"File name of the signed package (output)."_s);
     228         [ #  # ]:           0 :             clp.addPositionalArgument(u"certificate"_s,    u"PKCS#12 certificate file."_s);
     229         [ #  # ]:           0 :             clp.addPositionalArgument(u"password"_s,       u"Password for the PKCS#12 certificate."_s);
     230         [ #  # ]:           0 :             clp.addPositionalArgument(u"hardware-id"_s,    u"Unique hardware id to which this package gets bound."_s);
     231         [ #  # ]:           0 :             clp.process(a);
     232                 :             : 
     233   [ #  #  #  # ]:           0 :             if (clp.positionalArguments().size() != 6)
     234                 :           0 :                 clp.showHelp(1);
     235                 :             : 
     236   [ #  #  #  # ]:           0 :             p.reset(PackagingJob::storeSign(clp.positionalArguments().at(1),
     237         [ #  # ]:           0 :                                             clp.positionalArguments().at(2),
     238         [ #  # ]:           0 :                                             clp.positionalArguments().at(3),
     239         [ #  # ]:           0 :                                             clp.positionalArguments().at(4),
     240   [ #  #  #  # ]:           0 :                                             clp.positionalArguments().at(5),
     241         [ #  # ]:           0 :                                             clp.isSet(u"json"_s)));
     242                 :           0 :             break;
     243                 :             : 
     244                 :           0 :         case StoreVerifyPackage:
     245   [ #  #  #  # ]:           0 :             clp.addOption({ u"verbose"_s, u"Print details regarding the verification to stdout."_s });
     246         [ #  # ]:           0 :             clp.addPositionalArgument(u"package"_s,      u"File name of the signed package (input)."_s);
     247         [ #  # ]:           0 :             clp.addPositionalArgument(u"certificates"_s, u"Store CA certificate file(s)."_s, u"certificates..."_s);
     248         [ #  # ]:           0 :             clp.addPositionalArgument(u"hardware-id"_s,  u"Unique hardware id to which this package was bound."_s);
     249         [ #  # ]:           0 :             clp.process(a);
     250                 :             : 
     251   [ #  #  #  # ]:           0 :             if (clp.positionalArguments().size() < 4)
     252                 :           0 :                 clp.showHelp(1);
     253                 :             : 
     254   [ #  #  #  #  :           0 :             p.reset(PackagingJob::storeVerify(clp.positionalArguments().at(1),
                   #  # ]
     255   [ #  #  #  # ]:           0 :                                               clp.positionalArguments().mid(2, clp.positionalArguments().size() - 3),
     256   [ #  #  #  # ]:           0 :                                               *--clp.positionalArguments().cend()));
     257                 :           0 :             break;
     258                 :             : 
     259                 :           0 :         case YamlToJson: {
     260   [ #  #  #  #  :           0 :             clp.addOption({{ u"i"_s, u"document-index"_s }, u"Only output the specified YAML sub-document."_s, u"index"_s });
             #  #  #  # ]
     261         [ #  # ]:           0 :             clp.addPositionalArgument(u"yaml-file"_s, u"YAML file name, defaults to stdin (input)."_s);
     262         [ #  # ]:           0 :             clp.process(a);
     263                 :             : 
     264   [ #  #  #  # ]:           0 :             if (clp.positionalArguments().size() > 2)
     265                 :           0 :                 clp.showHelp(1);
     266                 :             : 
     267   [ #  #  #  # ]:           0 :             QString yamlName = (clp.positionalArguments().size() == 2) ? clp.positionalArguments().at(1)
     268         [ #  # ]:           0 :                                                                        : u"-"_s;
     269         [ #  # ]:           0 :             QFile yaml(yamlName);
     270         [ #  # ]:           0 :             if (yamlName == u"-") {
     271   [ #  #  #  # ]:           0 :                 if (!yaml.open(0, QIODevice::ReadOnly))
     272                 :           0 :                     throw Exception("Could not open stdin for reading");
     273   [ #  #  #  # ]:           0 :             } else if (!yaml.open(QIODevice::ReadOnly)) {
     274                 :           0 :                 throw Exception(yaml, "Could not open YAML input file");
     275                 :             :             }
     276                 :             : 
     277   [ #  #  #  # ]:           0 :             QVector<QVariant> docs = YamlParser::parseAllDocuments(yaml.readAll());
     278         [ #  # ]:           0 :             QJsonDocument json;
     279   [ #  #  #  # ]:           0 :             if (clp.isSet(u"i"_s)) {
     280                 :           0 :                 bool isInt;
     281         [ #  # ]:           0 :                 int index = clp.value(u"i"_s).toInt(&isInt);
     282   [ #  #  #  # ]:           0 :                 if (!isInt || index < 0) {
     283         [ #  # ]:           0 :                     throw Exception("Invalid document index specified: %1").arg(clp.value(u"i"_s));
     284         [ #  # ]:           0 :                 } else if (index >= docs.size()) {
     285                 :           0 :                     throw Exception("Requested YAML sub document at index %1, but only indices 0 to %2 are available in this document.")
     286                 :           0 :                             .arg(index).arg(docs.size() - 1);
     287                 :             :                 } else {
     288         [ #  # ]:           0 :                     json = QJsonDocument::fromVariant(docs.at(index));
     289                 :             :                 }
     290                 :             :             } else {
     291   [ #  #  #  # ]:           0 :                 json.setArray(QJsonArray::fromVariantList(docs.toList()));
     292                 :             :             }
     293   [ #  #  #  # ]:           0 :             fprintf(stdout, "%s", json.toJson(QJsonDocument::Indented).constData());
     294                 :           0 :             return 0;
     295                 :           0 :         }
     296                 :             :         }
     297                 :             : 
     298         [ #  # ]:           0 :         if (!p)
     299                 :             :             return 2;
     300                 :             : 
     301         [ #  # ]:           0 :         p->execute();
     302   [ #  #  #  #  :           0 :         if (clp.isSet(u"verbose"_s) && !p->output().isEmpty())
          #  #  #  #  #  
                #  #  # ]
     303   [ #  #  #  #  :           0 :             fprintf(stdout, "%s\n", qPrintable(p->output()));
                   #  # ]
     304         [ #  # ]:           0 :         return p->resultCode();
     305         [ #  # ]:           0 :     } catch (const Exception &e) {
     306   [ -  -  -  - ]:           0 :         fprintf(stderr, "ERROR: %s\n", qPrintable(e.errorString()));
     307                 :           0 :         return 1;
     308                 :           0 :     }
     309                 :           0 : }
        

Generated by: LCOV version 2.0-1