<commit_before>/****************************************************************************
**
** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
** Contact: http://www.qt-project.org/legal
**
** This file is part of the Qt Build Suite.
**
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and Digia.  For licensing terms and
** conditions see http://qt.digia.com/licensing.  For further information
** use the contact form at http://qt.digia.com/contact-us.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 2.1 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL included in the
** packaging of this file.  Please review the following information to
** ensure the GNU Lesser General Public License version 2.1 requirements
** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** In addition, as a special exception, Digia gives you certain additional
** rights.  These rights are described in the Digia Qt LGPL Exception
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
****************************************************************************/

#include "setupqt.h"

#include "../shared/logging/consolelogger.h"
#include <logging/translator.h>
#include <tools/hostosinfo.h>
#include <tools/profile.h>
#include <tools/settings.h>

#include <QByteArrayMatcher>
#include <QCoreApplication>
#include <QDir>
#include <QFileInfo>
#include <QProcess>
#include <QStringList>
#include <QtDebug>

namespace qbs {
using Internal::HostOsInfo;
using Internal::Tr;

const QString qmakeExecutableName = QLatin1String("qmake" QTC_HOST_EXE_SUFFIX);

struct Version
{
    Version()
        : majorVersion(0), minorVersion(0), patchLevel(0)
    {}

    int majorVersion;
    int minorVersion;
    int patchLevel;
};

static QStringList collectQmakePaths()
{
    QStringList qmakePaths;

    QByteArray enviromentPath = qgetenv("PATH");
    QList<QByteArray> enviromentPaths
            = enviromentPath.split(HostOsInfo::pathListSeparator().toLatin1());
    foreach (const QByteArray &path, enviromentPaths) {
        QFileInfo pathFileInfo(QDir(QLatin1String(path)), qmakeExecutableName);
        if (pathFileInfo.exists()) {
            QString qmakePath = pathFileInfo.absoluteFilePath();
            if (!qmakePaths.contains(qmakePath))
                qmakePaths.append(qmakePath);
        }
    }

    return qmakePaths;
}

bool SetupQt::isQMakePathValid(const QString &qmakePath)
{
    QFileInfo qmakeFileInfo(qmakePath);
    return qmakeFileInfo.exists() && qmakeFileInfo.isFile() && qmakeFileInfo.isExecutable();
}

QList<QtEnviroment> SetupQt::fetchEnviroments()
{
    QList<QtEnviroment> qtEnviroments;

    foreach (const QString &qmakePath, collectQmakePaths())
        qtEnviroments.append(fetchEnviroment(qmakePath));

    return qtEnviroments;
}

static QMap<QByteArray, QByteArray> qmakeQueryOutput(const QString &qmakePath)
{
    QProcess qmakeProcess;
    qmakeProcess.start(qmakePath, QStringList() << "-query");
    if (!qmakeProcess.waitForStarted())
        throw Error(SetupQt::tr("%1 cannot be started.").arg(qmakePath));
    qmakeProcess.waitForFinished();
    const QByteArray output = qmakeProcess.readAllStandardOutput();

    QMap<QByteArray, QByteArray> ret;
    foreach (const QByteArray &line, output.split('\n')) {
        int idx = line.indexOf(':');
        if (idx >= 0)
            ret[line.left(idx)] = line.mid(idx + 1).trimmed();
    }
    return ret;
}

static QByteArray readFileContent(const QString &filePath)
{
    QFile file(filePath);
    if (file.open(QFile::ReadOnly))
        return file.readAll();

    return QByteArray();
}

static QByteArray configVariable(const QByteArray &configContent, const QByteArray &key)
{
    QRegExp regularExpression(QString(".*%1\\s*\\+{0,1}=(.*)").arg(QString::fromLatin1(key)), Qt::CaseSensitive);

    QList<QByteArray> configContentLines = configContent.split('\n');

    bool success = false;

    foreach (const QByteArray &configContentLine, configContentLines) {
        success = regularExpression.exactMatch(configContentLine);
        if (success)
            break;
    }

    if (success)
        return regularExpression.capturedTexts()[1].simplified().toLatin1();

    return QByteArray();
}

static Version extractVersion(const QString &versionString)
{
    Version v;
    const QStringList parts = versionString.split('.', QString::SkipEmptyParts);
    const QList<int *> vparts = QList<int *>() << &v.majorVersion << &v.minorVersion << &v.patchLevel;
    const int c = qMin(parts.count(), vparts.count());
    for (int i = 0; i < c; ++i)
        *vparts[i] = parts.at(i).toInt();
    return v;
}

QtEnviroment SetupQt::fetchEnviroment(const QString &qmakePath)
{
    QtEnviroment qtEnvironment;
    QMap<QByteArray, QByteArray> queryOutput = qmakeQueryOutput(qmakePath);

    qtEnvironment.installPrefixPath = queryOutput.value("QT_INSTALL_PREFIX");
    qtEnvironment.documentationPath = queryOutput.value("QT_INSTALL_DOCS");
    qtEnvironment.includePath = queryOutput.value("QT_INSTALL_HEADERS");
    qtEnvironment.libraryPath = queryOutput.value("QT_INSTALL_LIBS");
    qtEnvironment.binaryPath = queryOutput.value("QT_INSTALL_BINS");
    qtEnvironment.pluginPath = queryOutput.value("QT_INSTALL_PLUGINS");
    qtEnvironment.qmlImportPath = queryOutput.value("QT_INSTALL_IMPORTS");
    qtEnvironment.qtVersion = queryOutput.value("QT_VERSION");

    const Version qtVersion = extractVersion(qtEnvironment.qtVersion);

    QByteArray mkspecsBasePath;
    if (qtVersion.majorVersion >= 5)
        mkspecsBasePath = queryOutput.value("QT_HOST_DATA") + "/mkspecs";
    else
        mkspecsBasePath = queryOutput.value("QT_INSTALL_DATA") + "/mkspecs";

    if (!QFile::exists(mkspecsBasePath))
        throw Error(tr("Cannot extract the mkspecs directory."));

    const QByteArray qconfigContent = readFileContent(mkspecsBasePath + "/qconfig.pri");
    qtEnvironment.qtMajorVersion = configVariable(qconfigContent, "QT_MAJOR_VERSION").toInt();
    qtEnvironment.qtMinorVersion = configVariable(qconfigContent, "QT_MINOR_VERSION").toInt();
    qtEnvironment.qtPatchVersion = configVariable(qconfigContent, "QT_PATCH_VERSION").toInt();
    qtEnvironment.qtNameSpace = configVariable(qconfigContent, "QT_NAMESPACE");
    qtEnvironment.qtLibInfix = configVariable(qconfigContent, "QT_LIBINFIX");
    qtEnvironment.configItems = QString(configVariable(qconfigContent, "CONFIG")).split(QLatin1Char(' '), QString::SkipEmptyParts);

    // read mkspec
    if (qtVersion.majorVersion >= 5) {
        const QString mkspecName = queryOutput.value("QMAKE_XSPEC");
        qtEnvironment.mkspecPath = mkspecsBasePath + QLatin1Char('/') + mkspecName;
    } else {
        if (HostOsInfo::isWindowsHost()) {
            const QByteArray fileContent = readFileContent(mkspecsBasePath + "/default/qmake.conf");
            qtEnvironment.mkspecPath = configVariable(fileContent, "QMAKESPEC_ORIGINAL");
        } else {
            qtEnvironment.mkspecPath = QFileInfo(mkspecsBasePath + "/default").symLinkTarget();
        }
    }

    if (!QFileInfo(qtEnvironment.mkspecPath).exists())
        throw Error(tr("mkspec '%1' does not exist").arg(qtEnvironment.mkspecPath));

    qtEnvironment.mkspecPath = QDir::toNativeSeparators(qtEnvironment.mkspecPath);
    return qtEnvironment;
}

void SetupQt::saveToQbsSettings(const QString &qtVersionName, const QtEnviroment &qtEnviroment,<commit_msg>setup-qt: clean up configVariable function<commit_after>/****************************************************************************
**
** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
** Contact: http://www.qt-project.org/legal
**
** This file is part of the Qt Build Suite.
**
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and Digia.  For licensing terms and
** conditions see http://qt.digia.com/licensing.  For further information
** use the contact form at http://qt.digia.com/contact-us.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 2.1 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL included in the
** packaging of this file.  Please review the following information to
** ensure the GNU Lesser General Public License version 2.1 requirements
** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** In addition, as a special exception, Digia gives you certain additional
** rights.  These rights are described in the Digia Qt LGPL Exception
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
****************************************************************************/

#include "setupqt.h"

#include "../shared/logging/consolelogger.h"
#include <logging/translator.h>
#include <tools/hostosinfo.h>
#include <tools/profile.h>
#include <tools/settings.h>

#include <QByteArrayMatcher>
#include <QCoreApplication>
#include <QDir>
#include <QFileInfo>
#include <QProcess>
#include <QStringList>
#include <QtDebug>

namespace qbs {
using Internal::HostOsInfo;
using Internal::Tr;

const QString qmakeExecutableName = QLatin1String("qmake" QTC_HOST_EXE_SUFFIX);

struct Version
{
    Version()
        : majorVersion(0), minorVersion(0), patchLevel(0)
    {}

    int majorVersion;
    int minorVersion;
    int patchLevel;
};

static QStringList collectQmakePaths()
{
    QStringList qmakePaths;

    QByteArray enviromentPath = qgetenv("PATH");
    QList<QByteArray> enviromentPaths
            = enviromentPath.split(HostOsInfo::pathListSeparator().toLatin1());
    foreach (const QByteArray &path, enviromentPaths) {
        QFileInfo pathFileInfo(QDir(QLatin1String(path)), qmakeExecutableName);
        if (pathFileInfo.exists()) {
            QString qmakePath = pathFileInfo.absoluteFilePath();
            if (!qmakePaths.contains(qmakePath))
                qmakePaths.append(qmakePath);
        }
    }

    return qmakePaths;
}

bool SetupQt::isQMakePathValid(const QString &qmakePath)
{
    QFileInfo qmakeFileInfo(qmakePath);
    return qmakeFileInfo.exists() && qmakeFileInfo.isFile() && qmakeFileInfo.isExecutable();
}

QList<QtEnviroment> SetupQt::fetchEnviroments()
{
    QList<QtEnviroment> qtEnviroments;

    foreach (const QString &qmakePath, collectQmakePaths())
        qtEnviroments.append(fetchEnviroment(qmakePath));

    return qtEnviroments;
}

static QMap<QByteArray, QByteArray> qmakeQueryOutput(const QString &qmakePath)
{
    QProcess qmakeProcess;
    qmakeProcess.start(qmakePath, QStringList() << "-query");
    if (!qmakeProcess.waitForStarted())
        throw Error(SetupQt::tr("%1 cannot be started.").arg(qmakePath));
    qmakeProcess.waitForFinished();
    const QByteArray output = qmakeProcess.readAllStandardOutput();

    QMap<QByteArray, QByteArray> ret;
    foreach (const QByteArray &line, output.split('\n')) {
        int idx = line.indexOf(':');
        if (idx >= 0)
            ret[line.left(idx)] = line.mid(idx + 1).trimmed();
    }
    return ret;
}

static QByteArray readFileContent(const QString &filePath)
{
    QFile file(filePath);
    if (file.open(QFile::ReadOnly))
        return file.readAll();

    return QByteArray();
}

static QString configVariable(const QByteArray &configContent, const QString &key)
{
    QRegExp regexp(QLatin1String("\\s*") + key + QLatin1String("\\s*\\+{0,1}=(.*)"),
                   Qt::CaseSensitive);

    QList<QByteArray> configContentLines = configContent.split('\n');

    bool success = false;

    foreach (const QByteArray &configContentLine, configContentLines) {
        success = regexp.exactMatch(configContentLine);
        if (success)
            break;
    }

    if (success)
        return regexp.capturedTexts()[1].simplified();

    return QString();
}

static Version extractVersion(const QString &versionString)
{
    Version v;
    const QStringList parts = versionString.split('.', QString::SkipEmptyParts);
    const QList<int *> vparts = QList<int *>() << &v.majorVersion << &v.minorVersion << &v.patchLevel;
    const int c = qMin(parts.count(), vparts.count());
    for (int i = 0; i < c; ++i)
        *vparts[i] = parts.at(i).toInt();
    return v;
}

QtEnviroment SetupQt::fetchEnviroment(const QString &qmakePath)
{
    QtEnviroment qtEnvironment;
    QMap<QByteArray, QByteArray> queryOutput = qmakeQueryOutput(qmakePath);

    qtEnvironment.installPrefixPath = queryOutput.value("QT_INSTALL_PREFIX");
    qtEnvironment.documentationPath = queryOutput.value("QT_INSTALL_DOCS");
    qtEnvironment.includePath = queryOutput.value("QT_INSTALL_HEADERS");
    qtEnvironment.libraryPath = queryOutput.value("QT_INSTALL_LIBS");
    qtEnvironment.binaryPath = queryOutput.value("QT_INSTALL_BINS");
    qtEnvironment.pluginPath = queryOutput.value("QT_INSTALL_PLUGINS");
    qtEnvironment.qmlImportPath = queryOutput.value("QT_INSTALL_IMPORTS");
    qtEnvironment.qtVersion = queryOutput.value("QT_VERSION");

    const Version qtVersion = extractVersion(qtEnvironment.qtVersion);

    QByteArray mkspecsBasePath;
    if (qtVersion.majorVersion >= 5)
        mkspecsBasePath = queryOutput.value("QT_HOST_DATA") + "/mkspecs";
    else
        mkspecsBasePath = queryOutput.value("QT_INSTALL_DATA") + "/mkspecs";

    if (!QFile::exists(mkspecsBasePath))
        throw Error(tr("Cannot extract the mkspecs directory."));

    const QByteArray qconfigContent = readFileContent(mkspecsBasePath + "/qconfig.pri");
    qtEnvironment.qtMajorVersion = configVariable(qconfigContent, "QT_MAJOR_VERSION").toInt();
    qtEnvironment.qtMinorVersion = configVariable(qconfigContent, "QT_MINOR_VERSION").toInt();
    qtEnvironment.qtPatchVersion = configVariable(qconfigContent, "QT_PATCH_VERSION").toInt();
    qtEnvironment.qtNameSpace = configVariable(qconfigContent, "QT_NAMESPACE");
    qtEnvironment.qtLibInfix = configVariable(qconfigContent, "QT_LIBINFIX");
    qtEnvironment.configItems = configVariable(qconfigContent, QLatin1String("CONFIG")).split(
                QLatin1Char(' '), QString::SkipEmptyParts);

    // read mkspec
    if (qtVersion.majorVersion >= 5) {
        const QString mkspecName = queryOutput.value("QMAKE_XSPEC");
        qtEnvironment.mkspecPath = mkspecsBasePath + QLatin1Char('/') + mkspecName;
    } else {
        if (HostOsInfo::isWindowsHost()) {
            const QByteArray fileContent = readFileContent(mkspecsBasePath + "/default/qmake.conf");
            qtEnvironment.mkspecPath = configVariable(fileContent, "QMAKESPEC_ORIGINAL");
        } else {
            qtEnvironment.mkspecPath = QFileInfo(mkspecsBasePath + "/default").symLinkTarget();
        }
    }

    if (!QFileInfo(qtEnvironment.mkspecPath).exists())
        throw Error(tr("mkspec '%1' does not exist").arg(qtEnvironment.mkspecPath));

    qtEnvironment.mkspecPath = QDir::toNativeSeparators(qtEnvironment.mkspecPath);
    return qtEnvironment;
}

void SetupQt::saveToQbsSettings(const QString &qtVersionName, const QtEnviroment &qtEnviroment,
{'fraction_non_alphanumeric': 0.13402590740028217, 'fraction_numerical': 0.004232397075798384, 'mean_word_length': 4.443280977312391, 'pattern_counts': {'":': 0, '<': 61, '<?xml version=': 0, '>': 49, 'https://': 0, 'lorem ipsum': 0, 'www.': 4, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3082067', 'n_tokens_mistral': 4810, 'n_tokens_neox': 4626, 'n_words': 1040}