1
/*
2
 * This file is part of the PySide project.
3
 *
4
 * Copyright (C) 2009-2010 Nokia Corporation and/or its subsidiary(-ies).
5
 *
6
 * Contact: PySide team <contact@pyside.org>
7
 *
8
 * This program is free software; you can redistribute it and/or
9
 * modify it under the terms of the GNU General Public License
10
 * version 2 as published by the Free Software Foundation.
11
 *
12
 * This program is distributed in the hope that it will be useful, but
13
 * WITHOUT ANY WARRANTY; without even the implied warranty of
14
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15
 * General Public License for more details.
16
 *
17
 * You should have received a copy of the GNU General Public License
18
 * along with this program; if not, write to the Free Software
19
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
20
 * 02110-1301 USA
21
 *
22
 */
23
24
#include <QCoreApplication>
25
#include <QLinkedList>
26
#include <QLibrary>
27
#include <QDomDocument>
28
#include <iostream>
29
#include <apiextractor.h>
30
#include "generatorrunnerconfig.h"
31
#include "generator.h"
32
33
#ifdef _WINDOWS
34
    #define PATH_SPLITTER ";"
35
#else
36
    #define PATH_SPLITTER ":"
37
#endif
38
39
static void printOptions(QTextStream& s, const QMap<QString, QString>& options) {
40
    QMap<QString, QString>::const_iterator it = options.constBegin();
41
    s.setFieldAlignment(QTextStream::AlignLeft);
42
    for (; it != options.constEnd(); ++it) {
43
        s << "  --";
44
        s.setFieldWidth(38);
45
        s << it.key() << it.value();
46
        s.setFieldWidth(0);
47
        s << endl;
48
    }
49
}
50
51
typedef void (*getGeneratorsFunc)(QLinkedList<Generator*>*);
52
53
static bool processProjectFile(QFile& projectFile, QMap<QString, QString>& args)
54
{
55
    QByteArray line = projectFile.readLine().trimmed();
56
    if (line.isEmpty() || line != "[generator-project]")
57
        return false;
58
59
    QStringList includePaths;
60
    QStringList typesystemPaths;
61
    QStringList apiVersions;
62
63
    while (!projectFile.atEnd()) {
64
        line = projectFile.readLine().trimmed();
65
        if (line.isEmpty())
66
            continue;
67
68
        int split = line.indexOf("=");
69
        QString key;
70
        QString value;
71
        if (split > 0) {
72
            key = line.left(split - 1).trimmed();
73
            value = line.mid(split + 1).trimmed();
74
        } else {
75
            key = line;
76
        }
77
78
        if (key == "include-path")
79
            includePaths << QDir::toNativeSeparators(value);
80
        else if (key == "typesystem-path")
81
            typesystemPaths << QDir::toNativeSeparators(value);
82
        else if (key == "api-version")
83
            apiVersions << value;
84
        else if (key == "header-file")
85
            args["arg-1"] = value;
86
        else if (key == "typesystem-file")
87
            args["arg-2"] = value;
88
        else
89
            args[key] = value;
90
    }
91
92
    if (!includePaths.isEmpty())
93
        args["include-paths"] = includePaths.join(PATH_SPLITTER);
94
95
    if (!typesystemPaths.isEmpty())
96
        args["typesystem-paths"] = typesystemPaths.join(PATH_SPLITTER);
97
    if (!apiVersions.isEmpty())
98
        args["api-version"] = apiVersions.join("|");
99
    return true;
100
}
101
102
static QMap<QString, QString> getInitializedArguments()
103
{
104
    QMap<QString, QString> args;
105
    QStringList arguments = QCoreApplication::arguments();
106
    QString appName = arguments.first();
107
    arguments.removeFirst();
108
109
    QString projectFileName;
110
    foreach (const QString& arg, arguments) {
111
        if (arg.startsWith("--project-file")) {
112
            int split = arg.indexOf("=");
113
            if (split > 0)
114
                projectFileName = arg.mid(split + 1).trimmed();
115
            break;
116
        }
117
    }
118
119
    if (projectFileName.isNull())
120
        return args;
121
122
    if (!QFile::exists(projectFileName)) {
123
        std::cerr << qPrintable(appName) << ": Project file \"";
124
        std::cerr << qPrintable(projectFileName) << "\" not found.";
125
        std::cerr << std::endl;
126
        return args;
127
    }
128
129
    QFile projectFile(projectFileName);
130
    if (!projectFile.open(QIODevice::ReadOnly))
131
        return args;
132
133
    if (!processProjectFile(projectFile, args)) {
134
        std::cerr << qPrintable(appName) << ": first line of project file \"";
135
        std::cerr << qPrintable(projectFileName) << "\" must be the string \"[generator-project]\"";
136
        std::cerr << std::endl;
137
        return args;
138
    }
139
140
    return args;
141
}
142
143
static QMap<QString, QString> getCommandLineArgs()
144
{
145
    QMap<QString, QString> args = getInitializedArguments();
146
    QStringList arguments = QCoreApplication::arguments();
147
    arguments.removeFirst();
148
149
    int argNum = 0;
150
    foreach (QString arg, arguments) {
151
        arg = arg.trimmed();
152
        if (arg.startsWith("--")) {
153
            int split = arg.indexOf("=");
154
            if (split > 0)
155
                args[arg.mid(2).left(split-2)] = arg.mid(split + 1).trimmed();
156
            else
157
                args[arg.mid(2)] = QString();
158
        } else if (arg.startsWith("-")) {
159
            args[arg.mid(1)] = QString();
160
        } else {
161
            argNum++;
162
            args[QString("arg-%1").arg(argNum)] = arg;
163
        }
164
    }
165
    return args;
166
}
167
168
void printUsage(const GeneratorList& generators)
169
{
170
    QTextStream s(stdout);
171
    s << "Usage:\n  "
172
    << "generator [options] header-file typesystem-file\n\n"
173
    "General options:\n";
174
    QMap<QString, QString> generalOptions;
175
    generalOptions.insert("project-file=<file>", "text file containing a description of the binding project. Replaces and overrides command line arguments");
176
    generalOptions.insert("debug-level=[sparse|medium|full]", "Set the debug level");
177
    generalOptions.insert("silent", "Avoid printing any message");
178
    generalOptions.insert("help", "Display this help and exit");
179
    generalOptions.insert("no-suppress-warnings", "Show all warnings");
180
    generalOptions.insert("output-directory=<path>", "The directory where the generated files will be written");
181
    generalOptions.insert("include-paths=<path>[" PATH_SPLITTER "<path>" PATH_SPLITTER "...]", "Include paths used by the C++ parser");
182
    generalOptions.insert("typesystem-paths=<path>[" PATH_SPLITTER "<path>" PATH_SPLITTER "...]", "Paths used when searching for typesystems");
183
    generalOptions.insert("documentation-only", "Do not generates any code, just the documentation");
184
    generalOptions.insert("license-file=<license-file>", "File used for copyright headers of generated files");
185
    generalOptions.insert("version", "Output version information and exit");
186
    generalOptions.insert("generator-set=<\"generator module\">", "generator-set to be used. e.g. qtdoc");
187
    generalOptions.insert("api-version=<\"package mask\">,<\"version\">", "Specify the supported api version used to generate the bindings");
188
    generalOptions.insert("drop-type-entries=\"<TypeEntry0>[;TypeEntry1;...]\"", "Semicolon separated list of type system entries (classes, namespaces, global functions and enums) to be dropped from generation.");
189
    printOptions(s, generalOptions);
190
191
    foreach (Generator* generator, generators) {
192
        QMap<QString, QString> options = generator->options();
193
        if (!options.isEmpty()) {
194
            s << endl << generator->name() << " options:\n";
195
            printOptions(s, generator->options());
196
        }
197
    }
198
}
199
200
int main(int argc, char *argv[])
201
{
202
    // needed by qxmlpatterns
203
    QCoreApplication app(argc, argv);
204
205
    // Store command arguments in a map
206
    QMap<QString, QString> args = getCommandLineArgs();
207
    GeneratorList generators;
208
209
    if (args.contains("version")) {
210
        std::cout << "generatorrunner v" GENERATORRUNNER_VERSION << std::endl;
211
        std::cout << "Copyright (C) 2009-2010 Nokia Corporation and/or its subsidiary(-ies)" << std::endl;
212
        return EXIT_SUCCESS;
213
    }
214
215
    // Try to load a generator
216
    QString generatorSet = args.value("generator-set");
217
218
    // Also check "generatorSet" command line argument for backward compatibility.
219
    if (generatorSet.isEmpty())
220
        generatorSet = args.value("generatorSet");
221
222
    if (!generatorSet.isEmpty()) {
223
        QFileInfo generatorFile(generatorSet);
224
225
        if (!generatorFile.exists()) {
226
            QString generatorSetName(generatorSet + "_generator" + MODULE_EXTENSION);
227
228
            // More library paths may be added via the QT_PLUGIN_PATH environment variable.
229
            QCoreApplication::addLibraryPath(GENERATORRUNNER_PLUGIN_DIR);
230
            foreach (const QString& path, QCoreApplication::libraryPaths()) {
231
                generatorFile.setFile(QDir(path), generatorSetName);
232
                if (generatorFile.exists())
233
                    break;
234
            }
235
        }
236
237
        if (!generatorFile.exists()) {
238
            std::cerr << argv[0] << ": Error loading generator-set plugin: ";
239
            std::cerr << qPrintable(generatorFile.baseName()) << " module not found." << std::endl;
240
            return EXIT_FAILURE;
241
        }
242
243
        QLibrary plugin(generatorFile.filePath());
244
        getGeneratorsFunc getGenerators = (getGeneratorsFunc)plugin.resolve("getGenerators");
245
        if (getGenerators) {
246
            getGenerators(&generators);
247
        } else {
248
            std::cerr << argv[0] << ": Error loading generator-set plugin: " << qPrintable(plugin.errorString()) << std::endl;
249
            return EXIT_FAILURE;
250
        }
251
    } else if (!args.contains("help")) {
252
        std::cerr << argv[0] << ": You need to specify a generator with --generator-set=GENERATOR_NAME" << std::endl;
253
        return EXIT_FAILURE;
254
    }
255
256
    if (args.contains("help")) {
257
        printUsage(generators);
258
        return EXIT_SUCCESS;
259
    }
260
261
262
    QString licenseComment;
263
    if (args.contains("license-file") && !args.value("license-file").isEmpty()) {
264
        QString licenseFileName = args.value("license-file");
265
        if (QFile::exists(licenseFileName)) {
266
            QFile licenseFile(licenseFileName);
267
            if (licenseFile.open(QIODevice::ReadOnly))
268
                licenseComment = licenseFile.readAll();
269
        } else {
270
            std::cerr << "Couldn't find the file containing the license heading: ";
271
            std::cerr << qPrintable(licenseFileName) << std::endl;
272
            return EXIT_FAILURE;
273
        }
274
    }
275
276
    QString outputDirectory = args.contains("output-directory") ? args["output-directory"] : "out";
277
    if (!QDir(outputDirectory).exists()) {
278
        if (!QDir().mkpath(outputDirectory)) {
279
            ReportHandler::warning("Can't create output directory: "+outputDirectory);
280
            return EXIT_FAILURE;
281
        }
282
    }
283
    // Create and set-up API Extractor
284
    ApiExtractor extractor;
285
    extractor.setLogDirectory(outputDirectory);
286
287
    if (args.contains("silent")) {
288
        extractor.setSilent(true);
289
    } else if (args.contains("debug-level")) {
290
        QString level = args.value("debug-level");
291
        if (level == "sparse")
292
            extractor.setDebugLevel(ReportHandler::SparseDebug);
293
        else if (level == "medium")
294
            extractor.setDebugLevel(ReportHandler::MediumDebug);
295
        else if (level == "full")
296
            extractor.setDebugLevel(ReportHandler::FullDebug);
297
    }
298
    if (args.contains("no-suppress-warnings"))
299
        extractor.setSuppressWarnings(false);
300
301
    if (args.contains("api-version")) {
302
        QStringList versions = args["api-version"].split("|");
303
        foreach (QString fullVersion, versions) {
304
            QStringList parts = fullVersion.split(",");
305
            QString package;
306
            QString version;
307
            package = parts.count() == 1 ? "*" : parts.first();
308
            version = parts.last();
309
            extractor.setApiVersion(package, version.toAscii());
310
        }
311
    }
312
313
    if (args.contains("drop-type-entries"))
314
        extractor.setDropTypeEntries(args["drop-type-entries"]);
315
316
    if (args.contains("typesystem-paths"))
317
        extractor.addTypesystemSearchPath(args.value("typesystem-paths").split(PATH_SPLITTER));
318
    if (!args.value("include-paths").isEmpty())
319
        extractor.addIncludePath(args.value("include-paths").split(PATH_SPLITTER));
320
321
322
    QString cppFileName = args.value("arg-1");
323
    QString typeSystemFileName = args.value("arg-2");
324
    if (args.contains("arg-3")) {
325
        std::cerr << "Too many arguments!" << std::endl;
326
        return EXIT_FAILURE;
327
    }
328
    extractor.setCppFileName(cppFileName);
329
    extractor.setTypeSystem(typeSystemFileName);
330
    if (!extractor.run())
331
        return EXIT_FAILURE;
332
333
    if (!extractor.classCount())
334
        ReportHandler::warning("No C++ classes found!");
335
336
    foreach (Generator* g, generators) {
337
        g->setOutputDirectory(outputDirectory);
338
        g->setLicenseComment(licenseComment);
339
        if (g->setup(extractor, args))
340
            g->generate();
341
    }
342
    qDeleteAll(generators);
343
344
    ReportHandler::flush();
345
    std::cout << "Done, " << ReportHandler::warningCount();
346
    std::cout << " warnings (" << ReportHandler::suppressedCount() << " known issues)";
347
    std::cout << std::endl;
348
}