From 61726b95c9c83f469d8027bc191223c163cc0a24 Mon Sep 17 00:00:00 2001 From: kh1 Date: Mon, 12 Nov 2012 15:46:06 +0100 Subject: refactoring mac relocate operation used by qt patch - improve the macreplaceinstallnamesoperation - remove unneeded macrelocateqt class and use the more generic macreplaceinstallnamesoperation Change-Id: I9d2c5bc5ea7ed56e09de8b327a9c8f9c02eb4f05 Reviewed-by: Karsten Heimrich --- src/libs/installer/installer.pro | 3 +- src/libs/installer/macrelocateqt.cpp | 99 ---------- src/libs/installer/macrelocateqt.h | 55 ------ .../installer/macreplaceinstallnamesoperation.cpp | 220 ++++++++------------- .../installer/macreplaceinstallnamesoperation.h | 34 ++-- src/libs/installer/qtpatchoperation.cpp | 37 ++-- 6 files changed, 122 insertions(+), 326 deletions(-) delete mode 100644 src/libs/installer/macrelocateqt.cpp delete mode 100644 src/libs/installer/macrelocateqt.h (limited to 'src/libs/installer') diff --git a/src/libs/installer/installer.pro b/src/libs/installer/installer.pro index 92da50551..437fb90a2 100644 --- a/src/libs/installer/installer.pro +++ b/src/libs/installer/installer.pro @@ -168,10 +168,9 @@ RESOURCES += resources/patch_file_lists.qrc \ resources/installer.qrc macx { - HEADERS += macrelocateqt.h \ + HEADERS += \ macreplaceinstallnamesoperation.h SOURCES += adminauthorization_mac.cpp \ - macrelocateqt.cpp \ macreplaceinstallnamesoperation.cpp } diff --git a/src/libs/installer/macrelocateqt.cpp b/src/libs/installer/macrelocateqt.cpp deleted file mode 100644 index 4ffdb8bd0..000000000 --- a/src/libs/installer/macrelocateqt.cpp +++ /dev/null @@ -1,99 +0,0 @@ -/************************************************************************** -** -** This file is part of Installer Framework -** -** Copyright (c) 2011-2012 Nokia Corporation and/or its subsidiary(-ies). -** -** Contact: Nokia Corporation (qt-info@nokia.com) -** -** -** GNU Lesser General Public License Usage -** -** 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. -** -** Other Usage -** -** Alternatively, this file may be used in accordance with the terms and -** conditions contained in a signed written agreement between you and Nokia. -** -** If you have questions regarding the use of this file, please contact -** Nokia at qt-info@nokia.com. -** -**************************************************************************/ - -#include "macrelocateqt.h" -#include "macreplaceinstallnamesoperation.h" - -#include -#include - - -using namespace QInstaller; - -Relocator::Relocator() -{ -} - -bool Relocator::apply(const QString &qtInstallDir, const QString &targetDir, const QString &version) -{ -// Relocator::apply(/Users/rakeller/QtSDKtest2/Simulator/Qt/gcc) -// Relocator uses indicator: /QtSDKtest2operation 'QtPatch' with arguments: 'mac; /Users/rakeller/QtSDKtest2/Simulator/Qt/gcc' failed: Error while relocating Qt: "ReplaceInsta - if (qtInstallDir.isEmpty()) { - m_errorMessage = QLatin1String("qtInstallDir can't be empty"); - return false; - } - if (targetDir.isEmpty()) { - m_errorMessage = QLatin1String("targetDir can't be empty"); - return false; - } - qDebug() << Q_FUNC_INFO << qtInstallDir; - - m_errorMessage.clear(); - m_installDir.clear(); - - m_installDir = targetDir; - if (!m_installDir.endsWith(QLatin1Char('/'))) - m_installDir.append(QLatin1Char('/')); - if (!QFile::exists(qtInstallDir + QLatin1String("/bin/qmake"))) { - m_errorMessage = QLatin1String("This is not a Qt installation directory."); - return false; - } - - QString indicator = qtInstallDir; - indicator = indicator.replace(targetDir, QString()); - //to get realy only the first subdirectory as an indicator like the old behaviour was till Mobility don't use this qt patch hack - indicator = indicator.left(indicator.indexOf(QLatin1String("/"), 1)); - - qDebug() << "Relocator uses indicator:" << indicator; - QString replacement = targetDir; - - - MacReplaceInstallNamesOperation operation; - operation.setComponentRootPath(qtInstallDir); - QStringList arguments; - arguments << indicator - << replacement; - if (version.isEmpty()) { - arguments << qtInstallDir + QLatin1String("/plugins") - << qtInstallDir + QLatin1String("/lib") - << qtInstallDir + QLatin1String("/imports") - << qtInstallDir + QLatin1String("/bin"); - } else { - arguments << qtInstallDir; - } - - operation.setArguments(arguments); - operation.performOperation(); - - m_errorMessage = operation.errorString(); - return m_errorMessage.isNull(); -} diff --git a/src/libs/installer/macrelocateqt.h b/src/libs/installer/macrelocateqt.h deleted file mode 100644 index 045847c41..000000000 --- a/src/libs/installer/macrelocateqt.h +++ /dev/null @@ -1,55 +0,0 @@ -/************************************************************************** -** -** This file is part of Installer Framework -** -** Copyright (c) 2010-2012 Nokia Corporation and/or its subsidiary(-ies). -** -** Contact: Nokia Corporation (qt-info@nokia.com) -** -** -** GNU Lesser General Public License Usage -** -** 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. -** -** Other Usage -** -** Alternatively, this file may be used in accordance with the terms and -** conditions contained in a signed written agreement between you and Nokia. -** -** If you have questions regarding the use of this file, please contact -** Nokia at qt-info@nokia.com. -** -**************************************************************************/ - -#ifndef RELOCATOR_H -#define RELOCATOR_H - -#include - -namespace QInstaller { - -class Relocator -{ -public: - Relocator(); - - bool apply(const QString &qtInstallDir, const QString &targetDir, const QString &version); - QString errorMessage() const { return m_errorMessage; } - -private: - QString m_errorMessage; - QString m_installDir; -}; - -} // namespace QInstaller - -#endif // RELOCATOR_H diff --git a/src/libs/installer/macreplaceinstallnamesoperation.cpp b/src/libs/installer/macreplaceinstallnamesoperation.cpp index 30b9451bd..bcd336661 100644 --- a/src/libs/installer/macreplaceinstallnamesoperation.cpp +++ b/src/libs/installer/macreplaceinstallnamesoperation.cpp @@ -34,18 +34,13 @@ #include "qprocesswrapper.h" -#include -#include -#include - +#include +#include +#include using namespace QInstaller; MacReplaceInstallNamesOperation::MacReplaceInstallNamesOperation() - : m_indicator(), - m_installationDir(), - m_componentRootPath() - { setName(QLatin1String("ReplaceInstallNames")); } @@ -57,8 +52,7 @@ void MacReplaceInstallNamesOperation::backup() bool MacReplaceInstallNamesOperation::performOperation() { // Arguments: - // 1. indicator to find the original build directory, - // means the path till this will be used to replace it with 2. + // 1. search string, means the beginning till that string will be replaced // 2. new/current target install directory(the replacement) // 3. directory containing frameworks // 4. other directory containing frameworks @@ -72,15 +66,19 @@ bool MacReplaceInstallNamesOperation::performOperation() return false; } - QString indicator = arguments().at(0); - QString installationDir = arguments().at(1); - QStringList searchDirList = arguments().mid(2); - foreach (const QString &searchDir, searchDirList) { - if (!apply(indicator, installationDir, searchDir)) - return false; + const QString searchString = arguments().at(0); + const QString installationDir = arguments().at(1); + const QStringList searchDirList = arguments().mid(2); + if (searchString.isEmpty() || installationDir.isEmpty() || searchDirList.isEmpty()) { + setError(InvalidArguments); + setErrorString(tr("One of the given arguments is empty. Argument1=%1; Argument2=%2, Argument3=%3") + .arg(searchString, installationDir, searchDirList.join(QLatin1String(" ")))); + return false; } + foreach (const QString &searchDir, searchDirList) + apply(searchString, installationDir, searchDir); - return true; + return error() == NoError; } bool MacReplaceInstallNamesOperation::undoOperation() @@ -98,60 +96,51 @@ Operation *MacReplaceInstallNamesOperation::clone() const return new MacReplaceInstallNamesOperation; } -bool MacReplaceInstallNamesOperation::apply(const QString &indicator, const QString &installationDir, - const QString &searchDir) +QSet MacReplaceInstallNamesOperation::collectPatchableBinaries(const QString &searchDir) { - m_indicator = indicator; - m_installationDir = installationDir; - - QStringList alreadyPatchedFrameworks; - QLatin1String frameworkSuffix(".framework"); - - QDirIterator dirIterator(searchDir, QDirIterator::Subdirectories | QDirIterator::FollowSymlinks); + QSet patchableBinaries; + QDirIterator dirIterator(searchDir, QDirIterator::Subdirectories); while (dirIterator.hasNext()) { - QString fileName = dirIterator.next(); - - //check that we don't do anything for already patched framework pathes - if (fileName.contains(frameworkSuffix)) { - QString alreadyPatchedSearchString = fileName.left(fileName.lastIndexOf(frameworkSuffix)) - + frameworkSuffix; - if (alreadyPatchedFrameworks.contains(alreadyPatchedSearchString)) { - continue; - } - } - if (dirIterator.fileInfo().isDir() && fileName.endsWith(frameworkSuffix)) { - relocateFramework(fileName); - alreadyPatchedFrameworks.append(fileName); - } - else if (dirIterator.fileInfo().isDir()) + const QString fileName = dirIterator.next(); + const QFileInfo fileInfo = dirIterator.fileInfo(); + if (fileInfo.isDir() || fileInfo.isSymLink()) continue; - else if (fileName.endsWith(QLatin1String(".dylib"))) - relocateBinary(fileName); + + MacBinaryInfo binaryInfo; + binaryInfo.fileName = fileName; + + // try to find libraries in frameworks + if (fileName.contains(QLatin1String(".framework/")) && !fileName.contains(QLatin1String("/Headers/")) + && updateExecutableInfo(&binaryInfo) == 0) { + patchableBinaries.insert(binaryInfo); + } else if (fileName.endsWith(QLatin1String(".dylib")) && updateExecutableInfo(&binaryInfo) == 0) { + patchableBinaries.insert(binaryInfo); + } // the endsWith checks are here because there might be wrongly committed files in the repositories else if (dirIterator.fileInfo().isExecutable() && !fileName.endsWith(QLatin1String(".h")) && !fileName.endsWith(QLatin1String(".cpp")) && !fileName.endsWith(QLatin1String(".pro")) - && !fileName.endsWith(QLatin1String(".pri"))) { - //the endsWith check are here because there were wrongly commited files in the repositories - relocateBinary(fileName); + && !fileName.endsWith(QLatin1String(".pri")) && updateExecutableInfo(&binaryInfo) == 0) { + patchableBinaries.insert(binaryInfo); } } - - return error() == NoError; + return patchableBinaries; } -void MacReplaceInstallNamesOperation::setComponentRootPath(const QString &path) +bool MacReplaceInstallNamesOperation::apply(const QString &searchString, const QString &replacement, + const QString &searchDir) { - m_componentRootPath = path; + foreach (const MacBinaryInfo &info, collectPatchableBinaries(searchDir)) + relocateBinary(info, searchString, replacement); + + return error() == NoError; } -void MacReplaceInstallNamesOperation::extractExecutableInfo(const QString &fileName, QString &frameworkId, - QStringList &frameworks, QString &originalBuildDir) +int MacReplaceInstallNamesOperation::updateExecutableInfo(MacBinaryInfo *binaryInfo) { - qDebug() << "Relocator calling otool -l for" << fileName; QProcessWrapper otool; - otool.start(QLatin1String("otool"), QStringList() << QLatin1String("-l") << fileName); + otool.start(QLatin1String("otool"), QStringList() << QLatin1String("-l") << binaryInfo->fileName); if (!otool.waitForStarted()) { setError(UserDefinedError, tr("Can't invoke otool. Is Xcode installed?")); - return; + return -1; } otool.waitForFinished(); enum State { @@ -161,11 +150,13 @@ void MacReplaceInstallNamesOperation::extractExecutableInfo(const QString &fileN }; State state = State_Start; QByteArray outputData = otool.readAllStandardOutput(); + if (outputData.contains("is not an object file")) + return -1; + QBuffer output(&outputData); output.open(QBuffer::ReadOnly); while (!output.atEnd()) { - QString line = QString::fromLocal8Bit(output.readLine()); - line = line.trimmed(); + QString line = QString::fromLocal8Bit(output.readLine()).trimmed(); if (line.startsWith(QLatin1String("cmd "))) { line.remove(0, 4); if (line == QLatin1String("LC_LOAD_DYLIB")) @@ -174,103 +165,54 @@ void MacReplaceInstallNamesOperation::extractExecutableInfo(const QString &fileN state = State_LC_ID_DYLIB; else state = State_Start; - } else if (state == State_LC_LOAD_DYLIB && line.startsWith(QLatin1String("name "))) { + } else if (state != State_Start && line.startsWith(QLatin1String("name "))) { line.remove(0, 5); int idx = line.indexOf(QLatin1String("(offset")); if (idx > 0) line.truncate(idx); line = line.trimmed(); - frameworks.append(line); - } else if (state == State_LC_ID_DYLIB && line.startsWith(QLatin1String("name "))) { - line.remove(0, 5); - int idx = line.indexOf(QLatin1String("(offset")); - if (idx > 0) - line.truncate(idx); - line = line.trimmed(); - frameworkId = line; - - originalBuildDir = frameworkId; - idx = originalBuildDir.indexOf(m_indicator); - if (idx < 0) { - originalBuildDir.clear(); - } else { - originalBuildDir.truncate(idx); - } - if (originalBuildDir.endsWith(QLatin1Char('/'))) - originalBuildDir.chop(1); - qDebug() << "originalBuildDir is:" << originalBuildDir; + if (state == State_LC_LOAD_DYLIB) + binaryInfo->dependentDynamicLibs.append(line); + if (state == State_LC_ID_DYLIB) + binaryInfo->dynamicLibId = line; } } - qDebug() << "END - Relocator calling otool -l for" << fileName; + return otool.exitCode(); } -void MacReplaceInstallNamesOperation::relocateBinary(const QString &fileName) +void MacReplaceInstallNamesOperation::relocateBinary(const MacBinaryInfo &info, const QString &searchString, + const QString &replacement) { - QString frameworkId; - QStringList frameworks; - QString originalBuildDir; - extractExecutableInfo(fileName, frameworkId, frameworks, originalBuildDir); - - qDebug() << QString::fromLatin1("Got the following information(fileName: %1, frameworkId: %2, frameworks: %3," - "orginalBuildDir: %4)").arg(fileName, frameworkId, frameworks.join(QLatin1String("|")), originalBuildDir); - - // Use regexp to find matches from frameworks and static libs - QRegExp frameworkRegexp(QLatin1String("Qt[0-9a-zA-Z]*\\.framework/")); - QRegExp dylibRegexp(QLatin1String("libQt.*\\.dylib")); - QStringList args; - // change framework ID only if Qt library reference - if (frameworkId.indexOf(frameworkRegexp) >= 0) { - args << QLatin1String("-id") << fileName << fileName; - if (!execCommand(QLatin1String("install_name_tool"), args)) - return; + qDebug() << QString::fromLatin1("Got the following information(fileName: %1, dynamicLibId: %2, " + "dynamicLibs: \n\t%3,").arg(info.fileName, info.dynamicLibId, info.dependentDynamicLibs + .join(QLatin1String("|"))); + + + // change framework ID only if dynamicLibId isn't only the filename, if it has no relative path ("@") + // and is not existing at the current looking for location + if (!info.dynamicLibId.isEmpty() && (info.dynamicLibId != QFileInfo(info.fileName).fileName()) + && !info.dynamicLibId.contains(QLatin1String("@")) && !QFileInfo(info.dynamicLibId).exists()) { + // error is set inside the execCommand method + if (!execCommand(QLatin1String("install_name_tool"), QStringList(QLatin1String("-id")) + << info.fileName << info.fileName)) { + return; + } } - // calculate path prefix which is the full installation path and - // /lib/ added so that it points to Qt installations libraries - QString prefix = m_componentRootPath + QLatin1String("/lib/"); - - // calculate path prefix which is the full installation path and - // /lib/ added so that it points to Qt installations libraries - foreach (const QString &fw, frameworks) { - int fraIndex = fw.indexOf(frameworkRegexp); - int dyIndex = fw.indexOf(dylibRegexp); - QString newPath; - if (fraIndex >= 0) - newPath = fw.mid(fraIndex); - else if (dyIndex >= 0) - newPath = fw.mid(dyIndex); - else + QStringList args; + foreach (const QString &dynamicLib, info.dependentDynamicLibs) { + if (!dynamicLib.contains(searchString)) continue; - - newPath = prefix + newPath; - args.clear(); - args << QLatin1String("-change") << fw << newPath << fileName; - if (!execCommand(QLatin1String("install_name_tool"), args)) - return; + args << QLatin1String("-change") << dynamicLib << replacement + dynamicLib.section(searchString, -1); } -} - -void MacReplaceInstallNamesOperation::relocateFramework(const QString &directoryName) -{ - QFileInfo fi(directoryName); - QString frameworkName = fi.baseName(); - QString absoluteVersionDirectory = directoryName + QLatin1String("/Versions/Current"); - if (QFileInfo(absoluteVersionDirectory).isSymLink()) { - absoluteVersionDirectory = QFileInfo(absoluteVersionDirectory).symLinkTarget(); - } - - fi.setFile(absoluteVersionDirectory + QDir::separator() + frameworkName); - if (fi.exists()) { - QString fileName = fi.isSymLink() ? fi.symLinkTarget() : fi.absoluteFilePath(); - relocateBinary(fileName); - } + // nothing found to patch + if (args.empty()) + return; - fi.setFile(absoluteVersionDirectory + QDir::separator() + frameworkName + QLatin1String("_debug")); - if (fi.exists()) { - QString fileName = fi.isSymLink() ? fi.symLinkTarget() : fi.absoluteFilePath(); - relocateBinary(fileName); - } + // error is set inside the execCommand method + // last argument is the file target which will be patched + execCommand(QLatin1String("install_name_tool"), args << info.fileName); } bool MacReplaceInstallNamesOperation::execCommand(const QString &cmd, const QStringList &args) @@ -285,7 +227,7 @@ bool MacReplaceInstallNamesOperation::execCommand(const QString &cmd, const QStr } process.waitForFinished(); if (process.exitCode() != 0) { - QString errorMessage = QLatin1String("Command %1 failed.\nArguments: %2\nOutput: %3\n"); + const QString errorMessage = QLatin1String("Command %1 failed.\nArguments: %2\nOutput: %3\n"); setError(UserDefinedError, errorMessage.arg(cmd, args.join(QLatin1String(" ")), QString::fromLocal8Bit(process.readAll()))); return false; diff --git a/src/libs/installer/macreplaceinstallnamesoperation.h b/src/libs/installer/macreplaceinstallnamesoperation.h index cad556394..aeae7eefd 100644 --- a/src/libs/installer/macreplaceinstallnamesoperation.h +++ b/src/libs/installer/macreplaceinstallnamesoperation.h @@ -37,6 +37,24 @@ namespace QInstaller { +struct MacBinaryInfo { + QString fileName; + QString dynamicLibId; // if that is empty, it is an executable + QStringList dependentDynamicLibs; + + uint qHash(const MacBinaryInfo &info); + bool operator==(const MacBinaryInfo &rhs) const + { + return fileName == rhs.fileName; + } + +}; + +inline uint qHash(const MacBinaryInfo &info) +{ + return qHash(info.fileName) ^ qHash(info.dynamicLibId); +} + class INSTALLER_EXPORT MacReplaceInstallNamesOperation : public Operation { public: @@ -48,20 +66,12 @@ public: bool testOperation(); Operation *clone() const; - bool apply(const QString &oldString, const QString &newString, const QString &frameworkDir); - void setComponentRootPath(const QString &path); - private: - void extractExecutableInfo(const QString &fileName, QString &frameworkId, QStringList &frameworks, - QString &originalBuildDir); - void relocateFramework(const QString &directoryName); - void relocateBinary(const QString &fileName); + bool apply(const QString &searchString, const QString &replacement, const QString &searchDir); + QSet collectPatchableBinaries(const QString &searchDir); + int updateExecutableInfo(MacBinaryInfo *binaryInfo); + void relocateBinary(const MacBinaryInfo &info, const QString &searchString, const QString &replacement); bool execCommand(const QString &cmd, const QStringList &args); - -private: - QString m_indicator; - QString m_installationDir; - QString m_componentRootPath; }; } // namespace QInstaller diff --git a/src/libs/installer/qtpatchoperation.cpp b/src/libs/installer/qtpatchoperation.cpp index 4941a00c3..270bd9853 100644 --- a/src/libs/installer/qtpatchoperation.cpp +++ b/src/libs/installer/qtpatchoperation.cpp @@ -33,8 +33,7 @@ #include "qtpatchoperation.h" #include "qtpatch.h" #ifdef Q_OS_MAC -#include "macrelocateqt.h" -#include "constants.h" +#include "macreplaceinstallnamesoperation.h" #endif #include "packagemanagercore.h" @@ -196,6 +195,23 @@ bool QtPatchOperation::performOperation() return false; } +#ifdef Q_OS_MAC + // just try to patch here at the beginning to keep the unpatched qmake if mac install_names_tool fails + MacReplaceInstallNamesOperation operation; + operation.setArguments(QStringList() + //can not use the old path which is wrong in the webkit case + //<< QString::fromUtf8(oldQtPath) + << QLatin1String("/lib/Qt") // search string + << newQtPathStr + QLatin1String("/lib/Qt") //replace string + << newQtPathStr //where + ); + if (!operation.performOperation()) { + setError(operation.error()); + setErrorString(operation.errorString()); + return false; + } +#endif + QString fileName; if (type == QLatin1String("windows")) fileName = QString::fromLatin1(":/files-to-patch-windows"); @@ -335,23 +351,6 @@ bool QtPatchOperation::performOperation() } //END - patch text files -#ifdef Q_OS_MAC - Relocator relocator; - bool successMacRelocating = false; - PackageManagerCore *const core = qVariantValue(value(QLatin1String("installer"))); - if (!core) { - setError(UserDefinedError); - setErrorString(tr("Needed installer object in \"%1\" operation is empty.").arg(name())); - return false; - } - Q_CHECK_PTR(core); - successMacRelocating = relocator.apply(newQtPathStr, core->value(scTargetDir), version); - if (!successMacRelocating) { - setError(UserDefinedError); - setErrorString(tr("Error while relocating Qt: %1").arg(relocator.errorMessage())); - return false; - } -#endif if (oldQtPathFromQMakeIsEmpty) { setError(UserDefinedError); setErrorString(tr("The installer was not able to get the unpatched path from \n%1.(maybe it is " -- cgit v1.2.3