Index: kdevplatform/util/path.cpp =================================================================== --- kdevplatform/util/path.cpp +++ kdevplatform/util/path.cpp @@ -23,10 +23,13 @@ #include "path.h" #include +#include #include #include +#include "util/debug.h" + using namespace KDevelop; namespace { @@ -397,13 +400,48 @@ return list; } +// determine an appropriate canonical representation of @p path +// or return the path unchanged if it doesn't correspond to an +// existing file or directory. +// For directories, resolve all and any symlinks using QFileInfo::canonicalFilePath() +// For files, resolve all symlinks in the path but leave the file untouched; +// this allows preserving fully specified executable paths where the executable +// is a symlink - the target application might check the name under which it's +// invoked to modify its behaviour. +static QString toCanonicalPath(const QString &path) +{ + const QFileInfo info(path); + if (info.exists()) { + if (info.isDir()) { + // the simple case: just resolve all symlinks in the entire path + return info.canonicalFilePath(); + } else { + // resolve the symlinks in the path to the file, and reappend the filename + return toCanonicalPath(info.absolutePath()) + QStringLiteral("/") + info.fileName(); + } + } + // don't attempt anything on non-existing files, it will fail + return path; +} + void Path::addPath(const QString& path) { if (path.isEmpty()) { return; } - const auto& newData = splitPath(path); + // convert local paths to canonical if not adding it to an existing path + const bool convertToCanonical = m_data.isEmpty() && path.startsWith(QLatin1Char('/')); + // Use QFileInfo::canonicalPath() to resolve any symlinks on the path, but not the + // final element (filename). This returns an empty string when path doesn't exist + // so we need to check the result. + const QString canonicalPath = convertToCanonical ? toCanonicalPath(path) : QString(); + if (UTIL().isDebugEnabled()) { + if (!canonicalPath.isEmpty() && canonicalPath != path) { + qCDebug(UTIL) << QStringLiteral("Path::addPath(%1): adding canonical form %2").arg(path).arg(canonicalPath); + } + } + const auto& newData = splitPath(canonicalPath.isEmpty() ? path : canonicalPath); if (newData.isEmpty()) { if (m_data.size() == (isRemote() ? 1 : 0)) { // this represents the root path, we just turned an invalid path into it @@ -421,6 +459,21 @@ std::copy(it, newData.end(), std::back_inserter(m_data)); cleanPath(&m_data, isRemote()); + // convert the concatenated result to canonical if local + // can surely be done in a more efficient manner + if (canonicalPath.isEmpty() && isLocalFile()) { + Path temp; + const QString newPath = generatePathOrUrl(true, true, m_data); + // using the default Path ctor is the fastest and easiest way + // to call ourselves with a fresh temporary m_data instance + temp.addPath(toCanonicalPath(newPath)); + if (!temp.m_data.isEmpty() && m_data != temp.m_data) { + if (UTIL().isDebugEnabled()) { + qCDebug(UTIL) << "Path::addPath(): new path is" << temp << "instead of" << newPath; + } + m_data = temp.m_data; + } + } } Path Path::parent() const