CMakePM: Use junctions for source/build dirs on Windows

This way we have fixed small (max 64 bytes) paths for CMake's configure
/ build / install steps.

This allows the user to have longer paths and still compile with MSVC /
GCC MinGW compilers.

Fixes: QTCREATORBUG-26786
Task-number: QTBUG-117413
Change-Id: I0cff6521626dd2ce78d0223d46f0b480e977d5c5
Reviewed-by: <github-actions-qt-creator@cristianadam.eu>
Reviewed-by: Alessandro Portale <alessandro.portale@qt.io>
This commit is contained in:
Cristian Adam
2024-01-31 09:49:56 +01:00
parent 656a30b407
commit e7e593b2f6
7 changed files with 185 additions and 3 deletions

View File

@@ -11,6 +11,7 @@
#include "cmakeprojectconstants.h"
#include "cmakeprojectmanagertr.h"
#include "cmaketool.h"
#include "cmaketoolmanager.h"
#include <android/androidconstants.h>
@@ -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) {

View File

@@ -13,6 +13,7 @@
#include "cmakeprojectconstants.h"
#include "cmakeprojectmanagertr.h"
#include "cmakespecificsettings.h"
#include "cmaketoolmanager.h"
#include "projecttreehelper.h"
#include <android/androidconstants.h>
@@ -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);

View File

@@ -8,6 +8,7 @@
#include "cmakeprojectconstants.h"
#include "cmakeprojectmanagertr.h"
#include "cmakespecificsettings.h"
#include "cmaketoolmanager.h"
#include <coreplugin/progressmanager/processprogress.h>
#include <projectexplorer/buildsystem.h>
@@ -144,7 +145,10 @@ void CMakeProcess::run(const BuildDirParameters &parameters, 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);

View File

@@ -11,6 +11,7 @@
#include <projectexplorer/projectexplorerconstants.h>
#include <utils/hostosinfo.h>
#include <utils/layoutbuilder.h>
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.<br><br>"
"They are stored under <tt>C:\\ProgramData\\QtCreator\\Links</tt> (overridable via "
"<tt>QTC_CMAKE_JUNCTIONS_DIR</tt> environment variable).<br><br>"
"With <tt>QTC_CMAKE_JUNCTIONS_HASH_LENGTH</tt> the MD5 hash key length can be shortened "
"to a value smaller than the default length value of 32.<br><br>"
"They are used for CMake configure, build and install operations."));
readSettings();
}

View File

@@ -19,6 +19,7 @@ public:
Utils::BoolAspect askBeforePresetsReload{this};
Utils::BoolAspect showSourceSubFolders{this};
Utils::BoolAspect showAdvancedOptionsByDefault{this};
Utils::BoolAspect useJunctionsForSourceAndBuildDirectories{this};
};
CMakeSpecificSettings &settings();

View File

@@ -25,19 +25,66 @@
#include <nanotrace/nanotrace.h>
#include <QCryptographicHash>
#include <QStandardPaths>
#include <stack>
#ifdef Q_OS_WIN
#include <qt_windows.h>
#include <winioctl.h>
// 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<std::unique_ptr<CMakeTool>> 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<char> buf(reparseDataLength + REPARSE_DATA_BUFFER_HEADER_SIZE, 0);
REPARSE_DATA_BUFFER &reparse = *reinterpret_cast<REPARSE_DATA_BUFFER *>(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<FilePath, QString> 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<Id> 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

View File

@@ -44,6 +44,8 @@ public:
static QString toolTipForRstHelpFile(const Utils::FilePath &helpFile);
static Utils::FilePath mappedFilePath(const Utils::FilePath &path);
public slots:
QList<Utils::Id> autoDetectCMakeForDevice(const Utils::FilePaths &searchPaths,
const QString &detectionSource,