qmake/generators/symbian/initprojectdeploy_symbian.cpp
author Dremov Kirill (Nokia-D-MSW/Tampere) <kirill.dremov@nokia.com>
Tue, 26 Jan 2010 12:42:25 +0200
changeset 2 56cd8111b7f7
parent 1 ae9c8dab0e3e
child 3 41300fa6a67c
permissions -rw-r--r--
Revision: 201001 Kit: 201004

/****************************************************************************
**
** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
** All rights reserved.
** Contact: Nokia Corporation (qt-info@nokia.com)
**
** This file is part of the qmake application of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL$
** No Commercial Usage
** This file contains pre-release code and may not be distributed.
** You may use this file in accordance with the terms and conditions
** contained in the Technology Preview License Agreement accompanying
** this package.
**
** 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, Nokia gives you certain additional
** rights.  These rights are described in the Nokia Qt LGPL Exception
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
** If you have questions regarding the use of this file, please contact
** Nokia at qt-info@nokia.com.
**
**
**
**
**
**
**
**
** $QT_END_LICENSE$
**
****************************************************************************/

#include "initprojectdeploy_symbian.h"
#include <QDirIterator>
#include <project.h>
#include <qxmlstream.h>
#include <qsettings.h>
#include <qdebug.h>

#define PLUGIN_STUB_DIR "qmakepluginstubs"
#define SYSBIN_DIR "\\sys\\bin"

#define SUFFIX_DLL "dll"
#define SUFFIX_EXE "exe"
#define SUFFIX_QTPLUGIN "qtplugin"

static void fixEpocRootStr(QString& path)
{
    path.replace("\\", "/");

    /* :QTP:QTPROD-154: Raptor needs the drive letter
    if (path.size() > 1 && path[1] == QChar(':')) {
        path = path.mid(2);
    }
    */

    if (!path.size() || path[path.size()-1] != QChar('/')) {
        path += QChar('/');
    }
}

#define SYMBIAN_SDKS_KEY "HKEY_LOCAL_MACHINE\\Software\\Symbian\\EPOC SDKs"

static QString epocRootStr;

QString epocRoot()
{
    if (!epocRootStr.isEmpty()) {
        return epocRootStr;
    }

    // First, check the env variable
    epocRootStr = qgetenv("EPOCROOT");

    if (epocRootStr.isEmpty()) {
        // No EPOCROOT set, check the default device
        // First check EPOCDEVICE env variable
        QString defaultDevice = qgetenv("EPOCDEVICE");

        // Check the windows registry via QSettings for devices.xml path
        QSettings settings(SYMBIAN_SDKS_KEY, QSettings::NativeFormat);
        QString devicesXmlPath = settings.value("CommonPath").toString();

        if (!devicesXmlPath.isEmpty()) {
            // Parse xml for correct device
            devicesXmlPath += "/devices.xml";
            QFile devicesFile(devicesXmlPath);
            if (devicesFile.open(QIODevice::ReadOnly)) {
                QXmlStreamReader xml(&devicesFile);
                while (!xml.atEnd()) {
                    xml.readNext();
                    if (xml.isStartElement() && xml.name() == "devices") {
                        if (xml.attributes().value("version") == "1.0") {
                            // Look for correct device
                            while (!(xml.isEndElement() && xml.name() == "devices") && !xml.atEnd()) {
                                xml.readNext();
                                if (xml.isStartElement() && xml.name() == "device") {
                                    if ((defaultDevice.isEmpty() && xml.attributes().value("default") == "yes") ||
                                        (!defaultDevice.isEmpty() && (xml.attributes().value("id").toString() + QString(":") + xml.attributes().value("name").toString()) == defaultDevice)) {
                                        // Found the correct device
                                        while (!(xml.isEndElement() && xml.name() == "device") && !xml.atEnd()) {
                                            xml.readNext();
                                            if (xml.isStartElement() && xml.name() == "epocroot") {
                                                epocRootStr = xml.readElementText();
                                                fixEpocRootStr(epocRootStr);
                                                return epocRootStr;
                                            }
                                        }
                                        xml.raiseError("No epocroot element found");
                                    }
                                }
                            }
                        } else {
                            xml.raiseError("Invalid 'devices' element version");
                        }
                    }
                }
                if (xml.hasError()) {
                    fprintf(stderr, "ERROR: \"%s\" when parsing devices.xml\n", qPrintable(xml.errorString()));
                }
            } else {
                fprintf(stderr, "Could not open devices.xml (%s)\n", qPrintable(devicesXmlPath));
            }
        } else {
            fprintf(stderr, "Could not retrieve " SYMBIAN_SDKS_KEY " setting\n");
        }

        fprintf(stderr, "Failed to determine epoc root.\n");
        if (!defaultDevice.isEmpty())
            fprintf(stderr, "The device indicated by EPOCDEVICE environment variable (%s) could not be found.\n", qPrintable(defaultDevice));
        fprintf(stderr, "Either set EPOCROOT or EPOCDEVICE environment variable to a valid value, or provide a default Symbian device.\n");

        // No valid device found; set epocroot to "/"
        epocRootStr = QLatin1String("/");
    }

    fixEpocRootStr(epocRootStr);
    return epocRootStr;
}


static bool isPlugin(const QFileInfo& info, const QString& devicePath)
{
    // Libraries are plugins if deployment path is something else than
    // SYSBIN_DIR with or without drive letter
    if (0 == info.suffix().compare(QLatin1String(SUFFIX_DLL), Qt::CaseInsensitive) &&
            (devicePath.size() < 8 ||
             (0 != devicePath.compare(QLatin1String(SYSBIN_DIR), Qt::CaseInsensitive) &&
              0 != devicePath.mid(1).compare(QLatin1String(":" SYSBIN_DIR), Qt::CaseInsensitive)))) {
        return true;
    } else {
        return false;
    }
}

static bool isBinary(const QFileInfo& info)
{
    if (0 == info.suffix().compare(QLatin1String(SUFFIX_DLL), Qt::CaseInsensitive) ||
            0 == info.suffix().compare(QLatin1String(SUFFIX_EXE), Qt::CaseInsensitive)) {
        return true;
    } else {
        return false;
    }
}

static void createPluginStub(const QFileInfo& info,
                             const QString& devicePath,
                             DeploymentList &deploymentList,
                             QStringList& generatedDirs,
                             QStringList& generatedFiles)
{
    QDir().mkpath(QLatin1String(PLUGIN_STUB_DIR "\\"));
    if (!generatedDirs.contains(PLUGIN_STUB_DIR))
        generatedDirs << PLUGIN_STUB_DIR;
    // Plugin stubs must have different name from the actual plugins, because
    // the toolchain for creating ROM images cannot handle non-binary .dll files properly.
    QFile stubFile(QLatin1String(PLUGIN_STUB_DIR "\\") + info.completeBaseName() + "." SUFFIX_QTPLUGIN);
    if (stubFile.open(QIODevice::WriteOnly)) {
        if (!generatedFiles.contains(stubFile.fileName()))
            generatedFiles << stubFile.fileName();
        QTextStream t(&stubFile);
        // Add note to stub so that people will not wonder what it is.
        // Creation date is added to make new stub to deploy always to
        // force plugin cache miss when loading plugins.
        t << "This file is a Qt plugin stub file. The real Qt plugin is located in " SYSBIN_DIR ". Created:" << QDateTime::currentDateTime().toString(Qt::ISODate) << "\n";
    } else {
        fprintf(stderr, "cannot deploy \"%s\" because of plugin stub file creation failed\n", info.fileName().toLatin1().constData());
    }
    QFileInfo stubInfo(stubFile);
    deploymentList.append(CopyItem(Option::fixPathToLocalOS(stubInfo.absoluteFilePath()),
                                   Option::fixPathToLocalOS(devicePath + "\\" + stubInfo.fileName())));
}

QString generate_uid(const QString& target)
{
    static QMap<QString, QString> targetToUid;

    QString tmp = targetToUid[target];

    if (!tmp.isEmpty()) {
        return tmp;
    }

    unsigned long hash = 5381;
    int c;

    for (int i = 0; i < target.size(); ++i) {
        c = target.at(i).toAscii();
        hash ^= c + ((c - i) << i % 20) + ((c + i) << (i + 5) % 20) + ((c - 2 * i) << (i + 10) % 20) + ((c + 2 * i) << (i + 15) % 20);
    }

    tmp.setNum(hash, 16);
    for (int i = tmp.size(); i < 8; ++i)
        tmp.prepend("0");

    targetToUid[target] = tmp;

    return tmp;
}

// UIDs starting with 0xE are test UIDs in symbian
QString generate_test_uid(const QString& target)
{
    QString tmp = generate_uid(target);
    tmp.replace(0, 1, "E");
    tmp.prepend("0x");

    return tmp;
}


void initProjectDeploySymbian(QMakeProject* project,
                              DeploymentList &deploymentList,
                              const QString &testPath,
                              bool deployBinaries,
                              const QString &platform,
                              const QString &build,
                              QStringList& generatedDirs,
                              QStringList& generatedFiles)
{
    QString targetPath = project->values("deploy.path").join(" ");
    if (targetPath.isEmpty())
        targetPath = testPath;
    if (targetPath.endsWith("/") || targetPath.endsWith("\\"))
        targetPath = targetPath.mid(0, targetPath.size() - 1);

    bool targetPathHasDriveLetter = false;
    if (targetPath.size() > 1) {
        targetPathHasDriveLetter = targetPath.at(1) == QLatin1Char(':');
    }
    QString deploymentDrive = targetPathHasDriveLetter ? targetPath.left(2) : QLatin1String("c:");

    foreach(QString item, project->values("DEPLOYMENT")) {
        QString devicePath = project->first(item + ".path");
        if (!deployBinaries
                && !devicePath.isEmpty()
                && (0 == devicePath.compare(project->values("APP_RESOURCE_DIR").join(""), Qt::CaseInsensitive)
                    || 0 == devicePath.compare(project->values("REG_RESOURCE_IMPORT_DIR").join(""), Qt::CaseInsensitive))) {
            // Do not deploy resources in emulator builds, as that seems to cause conflicts
            // If there is ever a real need to deploy pre-built resources for emulator,
            // BLD_INF_RULES.prj_exports can be used as a workaround.
            continue;
        }

        bool devicePathHasDriveLetter = false;
        if (devicePath.size() > 1) {
            devicePathHasDriveLetter = devicePath.at(1) == QLatin1Char(':');
        }

        if (devicePath.isEmpty() || devicePath == QLatin1String(".")) {
            devicePath = targetPath;
        }
        // check if item.path is relative (! either / or \)
        else if (!(devicePath.at(0) == QLatin1Char('/')
                   || devicePath.at(0) == QLatin1Char('\\')
                   || devicePathHasDriveLetter)) {
            // create output path
            devicePath = Option::fixPathToLocalOS(QDir::cleanPath(targetPath + QLatin1Char('\\') + devicePath));
        } else {
            if (0 == platform.compare(QLatin1String("winscw"), Qt::CaseInsensitive)) {
                if (devicePathHasDriveLetter) {
                    devicePath = epocRoot() + "epoc32\\winscw\\" + devicePath.remove(1, 1);
                } else {
                    devicePath = epocRoot() + "epoc32\\winscw\\c" + devicePath;
                }
            } else {
                // Drive letter needed if targetpath contains one and it is not already in
                //:QTP:QTPROD-92 Deployment of plugins requires WINSCW build before ARM build
                if (targetPathHasDriveLetter && !devicePathHasDriveLetter) {
                   //temporary fix for Raptor building for plugins
                   if (devicePath.indexOf("plugins", Qt::CaseInsensitive) != -1) {
                    devicePath = deploymentDrive + "\\epoc32\\data\\z" + devicePath;
				   } else {
                    devicePath = deploymentDrive + devicePath;
				   }
                } else {
                    devicePath = epocRoot() + "epoc32\\data\\z" + devicePath;
				   }
            }
        }

        devicePath.replace(QLatin1String("/"), QLatin1String("\\"));

        if (!deployBinaries &&
                0 == devicePath.right(8).compare(QLatin1String(SYSBIN_DIR), Qt::CaseInsensitive)) {
            // Skip deploying to SYSBIN_DIR for anything but binary deployments
            // Note: Deploying pre-built binaries also follow this rule, so emulator builds
            // will not get those deployed. Since there is no way to differentiate currently
            // between pre-built binaries for emulator and HW anyway, this is not a major issue.
            continue;
        }

        foreach(QString source, project->values(item + ".sources")) {
            source = Option::fixPathToLocalOS(source);
            QString nameFilter;
            QFileInfo info(source);
            QString searchPath;
            bool dirSearch = false;

            if (info.isDir()) {
                nameFilter = QLatin1String("*");
                searchPath = info.absoluteFilePath();
                dirSearch = true;
            } else {
                if (info.exists() || source.indexOf('*') != -1) {
                    nameFilter = source.split('\\').last();
                    searchPath = info.absolutePath();
                } else {
                    // Entry was not found. That is ok if it is a binary, since those do not necessarily yet exist.
                    // Dlls need to be processed even when not deploying binaries for the stubs
                    if (isBinary(info)) {
                        if (deployBinaries) {
                            // Executables and libraries are deployed to \sys\bin
                            QFileInfo releasePath(epocRoot() + "epoc32\\release\\" + platform + "\\" + build + "\\");
                            if(devicePathHasDriveLetter) {
                                deploymentList.append(CopyItem(Option::fixPathToLocalOS(releasePath.absolutePath() + "\\" + info.fileName(), false, true),
                                                               Option::fixPathToLocalOS(devicePath.left(2) + QLatin1String(SYSBIN_DIR "\\") + info.fileName())));
                            } else {
                                deploymentList.append(CopyItem(Option::fixPathToLocalOS(releasePath.absolutePath() + "\\" + info.fileName(), false, true),
                                                               Option::fixPathToLocalOS(deploymentDrive + QLatin1String(SYSBIN_DIR "\\") + info.fileName())));
                            }
                        }
                        if (isPlugin(info, devicePath)) {
                            createPluginStub(info, devicePath, deploymentList, generatedDirs, generatedFiles);
                            continue;
                        }
                    } else {
                        // Generate deployment even if file doesn't exist, as this may be the case
                        // when generating .pkg files.
                        deploymentList.append(CopyItem(Option::fixPathToLocalOS(info.absoluteFilePath()),
                                                       Option::fixPathToLocalOS(devicePath + "\\" + info.fileName())));
                        continue;
                    }
                }
            }

            int pathSize = info.absolutePath().size();
            QDirIterator iterator(searchPath, QStringList() << nameFilter
                                  , QDir::Files | QDir::NoDotAndDotDot | QDir::NoSymLinks
                                  , dirSearch ? QDirIterator::Subdirectories : QDirIterator::NoIteratorFlags);

            while (iterator.hasNext()) {
                iterator.next();
                QFileInfo iteratorInfo(iterator.filePath());
                QString absoluteItemPath = Option::fixPathToLocalOS(iteratorInfo.absolutePath());
                int diffSize = absoluteItemPath.size() - pathSize;

                if (!iteratorInfo.isDir()) {
                    if (isPlugin(iterator.fileInfo(), devicePath)) {
                        // This deploys pre-built plugins. Other pre-built binaries will deploy normally,
                        // as they have SYSBIN_DIR target path.
                        if (deployBinaries) {
                            deploymentList.append(CopyItem(Option::fixPathToLocalOS(absoluteItemPath + "\\" + iterator.fileName()),
                                                           Option::fixPathToLocalOS(deploymentDrive + QLatin1String(SYSBIN_DIR "\\") + iterator.fileName())));
                        }
                        createPluginStub(info, devicePath + "\\" + absoluteItemPath.right(diffSize), deploymentList, generatedDirs, generatedFiles);
                        continue;
                    } else {
                        deploymentList.append(CopyItem(Option::fixPathToLocalOS(absoluteItemPath + "\\" + iterator.fileName()),
                                                       Option::fixPathToLocalOS(devicePath + "\\" + absoluteItemPath.right(diffSize) + "\\" + iterator.fileName())));
                    }
                }
            }
        }
    }
}

//:QTP:QTPROD-92 Deployment of plugins requires WINSCW build before ARM build
void writeSbsDeploymentList(const DeploymentList& depList, QTextStream& t)
{
    for (int i = 0; i < depList.size(); ++i) {
        t << "START EXTENSION qt/qmake_emulator_deployment" << endl;
        QString fromItem = depList.at(i).from;
        QString toItem = depList.at(i).to;
        fromItem.replace("\\", "/");
        toItem.replace("\\", "/");
        t << "OPTION DEPLOY_SOURCE " << fromItem << endl;
        t << "OPTION DEPLOY_TARGET " << toItem << endl;
        t << "END" << endl;
    }
}