diff --git a/src/plugins/cmakeprojectmanager/cmakebuildstep.cpp b/src/plugins/cmakeprojectmanager/cmakebuildstep.cpp index 7d57342e395..6663b311a42 100644 --- a/src/plugins/cmakeprojectmanager/cmakebuildstep.cpp +++ b/src/plugins/cmakeprojectmanager/cmakebuildstep.cpp @@ -11,6 +11,7 @@ #include "cmakeprojectconstants.h" #include "cmakeprojectmanagertr.h" #include "cmaketool.h" +#include "cmaketoolmanager.h" #include @@ -438,7 +439,7 @@ CommandLine CMakeBuildStep::cmakeCommand() const if (buildConfiguration()) buildDirectory = buildConfiguration()->buildDirectory(); - cmd.addArgs({"--build", buildDirectory.path()}); + cmd.addArgs({"--build", CMakeToolManager::mappedFilePath(buildDirectory).path()}); cmd.addArg("--target"); cmd.addArgs(Utils::transform(m_buildTargets, [this](const QString &s) { diff --git a/src/plugins/cmakeprojectmanager/cmakebuildsystem.cpp b/src/plugins/cmakeprojectmanager/cmakebuildsystem.cpp index bd0bf3b774b..b102aa2d2ac 100644 --- a/src/plugins/cmakeprojectmanager/cmakebuildsystem.cpp +++ b/src/plugins/cmakeprojectmanager/cmakebuildsystem.cpp @@ -13,6 +13,7 @@ #include "cmakeprojectconstants.h" #include "cmakeprojectmanagertr.h" #include "cmakespecificsettings.h" +#include "cmaketoolmanager.h" #include "projecttreehelper.h" #include @@ -2292,7 +2293,7 @@ MakeInstallCommand CMakeBuildSystem::makeInstallCommand(const FilePath &installR buildDirectory = bc->buildDirectory(); cmd.command.addArg("--build"); - cmd.command.addArg(buildDirectory.path()); + cmd.command.addArg(CMakeToolManager::mappedFilePath(buildDirectory).path()); cmd.command.addArg("--target"); cmd.command.addArg(installTarget); diff --git a/src/plugins/cmakeprojectmanager/cmakeprocess.cpp b/src/plugins/cmakeprojectmanager/cmakeprocess.cpp index 0542e5b71c4..ef120914def 100644 --- a/src/plugins/cmakeprojectmanager/cmakeprocess.cpp +++ b/src/plugins/cmakeprojectmanager/cmakeprocess.cpp @@ -8,6 +8,7 @@ #include "cmakeprojectconstants.h" #include "cmakeprojectmanagertr.h" #include "cmakespecificsettings.h" +#include "cmaketoolmanager.h" #include #include @@ -144,7 +145,10 @@ void CMakeProcess::run(const BuildDirParameters ¶meters, const QStringList & }); CommandLine commandLine(cmakeExecutable); - commandLine.addArgs({"-S", sourceDirectory.path(), "-B", buildDirectory.path()}); + commandLine.addArgs({"-S", + CMakeToolManager::mappedFilePath(sourceDirectory).path(), + "-B", + CMakeToolManager::mappedFilePath(buildDirectory).path()}); commandLine.addArgs(arguments); TaskHub::clearTasks(ProjectExplorer::Constants::TASK_CATEGORY_BUILDSYSTEM); diff --git a/src/plugins/cmakeprojectmanager/cmakespecificsettings.cpp b/src/plugins/cmakeprojectmanager/cmakespecificsettings.cpp index efc8d242d9b..d814ee81350 100644 --- a/src/plugins/cmakeprojectmanager/cmakespecificsettings.cpp +++ b/src/plugins/cmakeprojectmanager/cmakespecificsettings.cpp @@ -11,6 +11,7 @@ #include +#include #include using namespace Utils; @@ -34,6 +35,7 @@ CMakeSpecificSettings::CMakeSpecificSettings() askBeforePresetsReload, showSourceSubFolders, showAdvancedOptionsByDefault, + useJunctionsForSourceAndBuildDirectories, st }; }); @@ -87,6 +89,21 @@ CMakeSpecificSettings::CMakeSpecificSettings() showAdvancedOptionsByDefault.setLabelText( ::CMakeProjectManager::Tr::tr("Show advanced options by default")); + useJunctionsForSourceAndBuildDirectories.setSettingsKey( + "UseJunctionsForSourceAndBuildDirectories"); + useJunctionsForSourceAndBuildDirectories.setDefaultValue(false); + useJunctionsForSourceAndBuildDirectories.setLabelText(::CMakeProjectManager::Tr::tr( + "Use Junctions for CMake configuration and build operations")); + useJunctionsForSourceAndBuildDirectories.setVisible(Utils::HostOsInfo().isWindowsHost()); + useJunctionsForSourceAndBuildDirectories.setToolTip(::CMakeProjectManager::Tr::tr( + "Create and use junctions for the source and build directories. This helps to overcome " + "issues with long paths on Windows.

" + "They are stored under C:\\ProgramData\\QtCreator\\Links (overridable via " + "QTC_CMAKE_JUNCTIONS_DIR environment variable).

" + "With QTC_CMAKE_JUNCTIONS_HASH_LENGTH the MD5 hash key length can be shortened " + "to a value smaller than the default length value of 32.

" + "They are used for CMake configure, build and install operations.")); + readSettings(); } diff --git a/src/plugins/cmakeprojectmanager/cmakespecificsettings.h b/src/plugins/cmakeprojectmanager/cmakespecificsettings.h index d75ba5abd13..cbec63dffe4 100644 --- a/src/plugins/cmakeprojectmanager/cmakespecificsettings.h +++ b/src/plugins/cmakeprojectmanager/cmakespecificsettings.h @@ -19,6 +19,7 @@ public: Utils::BoolAspect askBeforePresetsReload{this}; Utils::BoolAspect showSourceSubFolders{this}; Utils::BoolAspect showAdvancedOptionsByDefault{this}; + Utils::BoolAspect useJunctionsForSourceAndBuildDirectories{this}; }; CMakeSpecificSettings &settings(); diff --git a/src/plugins/cmakeprojectmanager/cmaketoolmanager.cpp b/src/plugins/cmakeprojectmanager/cmaketoolmanager.cpp index cf63ce791f5..46aa90e238d 100644 --- a/src/plugins/cmakeprojectmanager/cmaketoolmanager.cpp +++ b/src/plugins/cmakeprojectmanager/cmaketoolmanager.cpp @@ -25,19 +25,66 @@ #include +#include +#include #include +#ifdef Q_OS_WIN +#include +#include + +// taken from qtbase/src/corelib/io/qfilesystemengine_win.cpp +#if !defined(REPARSE_DATA_BUFFER_HEADER_SIZE) +typedef struct _REPARSE_DATA_BUFFER { + ULONG ReparseTag; + USHORT ReparseDataLength; + USHORT Reserved; + union { + struct { + USHORT SubstituteNameOffset; + USHORT SubstituteNameLength; + USHORT PrintNameOffset; + USHORT PrintNameLength; + ULONG Flags; + WCHAR PathBuffer[1]; + } SymbolicLinkReparseBuffer; + struct { + USHORT SubstituteNameOffset; + USHORT SubstituteNameLength; + USHORT PrintNameOffset; + USHORT PrintNameLength; + WCHAR PathBuffer[1]; + } MountPointReparseBuffer; + struct { + UCHAR DataBuffer[1]; + } GenericReparseBuffer; + }; +} REPARSE_DATA_BUFFER, *PREPARSE_DATA_BUFFER; +# define REPARSE_DATA_BUFFER_HEADER_SIZE FIELD_OFFSET(REPARSE_DATA_BUFFER, GenericReparseBuffer) +#endif // !defined(REPARSE_DATA_BUFFER_HEADER_SIZE) + +#ifndef FSCTL_SET_REPARSE_POINT +#define FSCTL_SET_REPARSE_POINT CTL_CODE(FILE_DEVICE_FILE_SYSTEM,41,METHOD_BUFFERED,FILE_ANY_ACCESS) +#endif +#endif + using namespace Core; using namespace Utils; namespace CMakeProjectManager { +static Q_LOGGING_CATEGORY(cmakeToolManagerLog, "qtc.cmake.toolmanager", QtWarningMsg); + class CMakeToolManagerPrivate { public: Id m_defaultCMake; std::vector> m_cmakeTools; Internal::CMakeToolSettingsAccessor m_accessor; + FilePath m_junctionsDir; + int m_junctionsHashLength = 32; + + CMakeToolManagerPrivate(); }; class HtmlHandler : public rst::ContentHandler @@ -313,6 +360,65 @@ void CMakeToolManager::updateDocumentation() Core::HelpManager::registerDocumentation(docs); } +static void createJunction(const FilePath &from, const FilePath &to) +{ +#ifdef Q_OS_WIN + to.createDir(); + const QString toString = to.path(); + + HANDLE handle = ::CreateFile((wchar_t *) toString.utf16(), + GENERIC_WRITE, + 0, + nullptr, + OPEN_EXISTING, + FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT, + nullptr); + if (handle == INVALID_HANDLE_VALUE) { + qCDebug(cmakeToolManagerLog()) + << "Failed to open" << toString << "to create a junction." << ::GetLastError(); + return; + } + + QString fromString("\\??\\"); + fromString.append(from.absoluteFilePath().nativePath()); + + auto fromStringLength = uint16_t(fromString.length() * sizeof(wchar_t)); + auto toStringLength = uint16_t(toString.length() * sizeof(wchar_t)); + auto reparseDataLength = fromStringLength + toStringLength + 12; + + std::vector buf(reparseDataLength + REPARSE_DATA_BUFFER_HEADER_SIZE, 0); + REPARSE_DATA_BUFFER &reparse = *reinterpret_cast(buf.data()); + + reparse.ReparseTag = IO_REPARSE_TAG_MOUNT_POINT; + reparse.ReparseDataLength = reparseDataLength; + + reparse.MountPointReparseBuffer.SubstituteNameOffset = 0; + reparse.MountPointReparseBuffer.SubstituteNameLength = fromStringLength; + fromString.toWCharArray(reparse.MountPointReparseBuffer.PathBuffer); + + reparse.MountPointReparseBuffer.PrintNameOffset = fromStringLength + sizeof(UNICODE_NULL); + reparse.MountPointReparseBuffer.PrintNameLength = toStringLength; + toString.toWCharArray(reparse.MountPointReparseBuffer.PathBuffer + fromString.length() + 1); + + DWORD retsize = 0; + if (!::DeviceIoControl(handle, + FSCTL_SET_REPARSE_POINT, + &reparse, + uint16_t(buf.size()), + nullptr, + 0, + &retsize, + nullptr)) { + qCDebug(cmakeToolManagerLog()) << "Failed to create junction from" << fromString << "to" + << toString << "GetLastError:" << ::GetLastError(); + } + ::CloseHandle(handle); +#else + Q_UNUSED(from) + Q_UNUSED(to) +#endif +} + QString CMakeToolManager::toolTipForRstHelpFile(const FilePath &helpFile) { static QHash map; @@ -335,6 +441,32 @@ QString CMakeToolManager::toolTipForRstHelpFile(const FilePath &helpFile) return tooltip; } +FilePath CMakeToolManager::mappedFilePath(const FilePath &path) +{ + if (!HostOsInfo::isWindowsHost()) + return path; + + if (path.needsDevice()) + return path; + + Internal::settings(); + if (!Internal::settings().useJunctionsForSourceAndBuildDirectories()) + return path; + + if (!d->m_junctionsDir.isDir()) + return path; + + const auto hashPath = QString::fromUtf8( + QCryptographicHash::hash(path.path().toUtf8(), QCryptographicHash::Md5).toHex(0)); + const auto fullHashPath = d->m_junctionsDir.pathAppended( + hashPath.left(d->m_junctionsHashLength)); + + if (!fullHashPath.exists()) + createJunction(path, fullHashPath); + + return fullHashPath.exists() ? fullHashPath : path; +} + QList CMakeToolManager::autoDetectCMakeForDevice(const FilePaths &searchPaths, const QString &detectionSource, QString *logMessage) @@ -441,4 +573,28 @@ void Internal::setupCMakeToolManager(QObject *guard) m_instance->setParent(guard); } +CMakeToolManagerPrivate::CMakeToolManagerPrivate() +{ + if (HostOsInfo::isWindowsHost()) { + const QStringList locations = QStandardPaths::standardLocations( + QStandardPaths::GenericConfigLocation); + m_junctionsDir = FilePath::fromString(*std::min_element(locations.begin(), locations.end())) + .pathAppended("QtCreator/Links"); + + if (Utils::qtcEnvironmentVariableIsSet("QTC_CMAKE_JUNCTIONS_DIR")) { + m_junctionsDir = FilePath::fromUserInput( + Utils::qtcEnvironmentVariable("QTC_CMAKE_JUNCTIONS_DIR")); + } + if (Utils::qtcEnvironmentVariableIsSet("QTC_CMAKE_JUNCTIONS_HASH_LENGTH")) { + bool ok = false; + const int hashLength + = Utils::qtcEnvironmentVariableIntValue("QTC_CMAKE_JUNCTIONS_HASH_LENGTH", &ok); + if (ok && hashLength >= 4 && hashLength < 32) + m_junctionsHashLength = hashLength; + } + if (!m_junctionsDir.exists()) + m_junctionsDir.createDir(); + } +} + } // CMakeProjectManager diff --git a/src/plugins/cmakeprojectmanager/cmaketoolmanager.h b/src/plugins/cmakeprojectmanager/cmaketoolmanager.h index 3377e1733f5..1836f36c860 100644 --- a/src/plugins/cmakeprojectmanager/cmaketoolmanager.h +++ b/src/plugins/cmakeprojectmanager/cmaketoolmanager.h @@ -44,6 +44,8 @@ public: static QString toolTipForRstHelpFile(const Utils::FilePath &helpFile); + static Utils::FilePath mappedFilePath(const Utils::FilePath &path); + public slots: QList autoDetectCMakeForDevice(const Utils::FilePaths &searchPaths, const QString &detectionSource,