diff --git a/src/plugins/mcusupport/mcusupportoptions.cpp b/src/plugins/mcusupport/mcusupportoptions.cpp index cd87896803a..1c7a170f8a9 100644 --- a/src/plugins/mcusupport/mcusupportoptions.cpp +++ b/src/plugins/mcusupport/mcusupportoptions.cpp @@ -30,23 +30,45 @@ using namespace Utils; namespace McuSupport::Internal { -static const Utils::FilePath expandWildcards(const Utils::FilePath& path) +// Utils::FileFilter do not support globbing with "*" placed in the middle of the path, +// since it is required for paths such as "Microsoft Visual Studio/2019/*/VC/Tools/MSVC/*/bin/Hostx64/x64" +// The filter is applied for each time a wildcard character is found in a path component. +// Returns a pair of the longest path if multiple ones exists and the number of components that were not found. +static const std::pair expandWildcards( + const FilePath path, const QList patternComponents) { - if (!path.fileName().contains("*") && !path.fileName().contains("?")) - return path; + // Only absolute paths are currently supported + // Call FilePath::cleanPath on the path before calling this function + if (!path.exists() || path.isRelativePath()) + return {path, patternComponents.size()}; - const FilePath p = path.parentDir(); + // All components are found + if (patternComponents.empty()) + return {path, patternComponents.size()}; - auto entries = p.dirEntries( - Utils::FileFilter({path.fileName()}, QDir::Dirs | QDir::Files | QDir::NoDotAndDotDot)); + const QString currentComponent = patternComponents.front().toString(); + FilePath currentPath = path / currentComponent; - if (entries.isEmpty()) - return path; + if (!currentComponent.contains("*") && !currentComponent.contains("?") && currentPath.exists()) + return expandWildcards(path / currentComponent, + {patternComponents.constBegin() + 1, patternComponents.constEnd()}); + + auto entries = path.dirEntries( + Utils::FileFilter({currentComponent}, QDir::Dirs | QDir::Files | QDir::NoDotAndDotDot)); + + std::pair retPair = {path, patternComponents.size()}; - // Return the last match (can correspond to the latest version) sort(entries, [](const FilePath &a, const FilePath &b) { return a.fileName() < b.fileName(); }); + for (const auto &entry : entries) { + auto [entry_path, remaining_components] = expandWildcards(entry, + {patternComponents.constBegin() + + 1, + patternComponents.constEnd()}); + if (remaining_components <= retPair.second) + retPair = {entry_path, remaining_components}; + } - return entries.last(); + return retPair; } Macros *McuSdkRepository::globalMacros() @@ -60,7 +82,32 @@ void McuSdkRepository::expandVariablesAndWildcards() for (const auto &target : std::as_const(mcuTargets)) { auto macroExpander = getMacroExpander(*target); for (const auto &package : target->packages()) { - package->setPath(expandWildcards(macroExpander->expand(package->path()))); + // Expand variables + const auto path = macroExpander->expand(package->path()); + + //expand wildcards + // Ignore expanding if no wildcards are found + if (!path.path().contains("*") && !path.path().contains("?")) { + package->setPath(path); + continue; + } + + QStringList pathComponents = path.cleanPath().path().split("/"); + + // Path components example on linux: {"", "home", "username"} + // Path components example on windows: {"C:", "Users", "username"} + // 2 for empty_split_entry(linux)|root(windows) + at least one component + if (pathComponents.size() < 2) { + package->setPath(path); + continue; + } + // drop empty_split_entry(linux)|root(windows) + pathComponents.pop_front(); + + package->setPath( + expandWildcards(FilePath::fromString(QDir::rootPath()), + {pathComponents.constBegin(), pathComponents.constEnd()}) + .first); } } } diff --git a/src/plugins/mcusupport/test/unittest.cpp b/src/plugins/mcusupport/test/unittest.cpp index b6fe45d2aeb..cef1ac70e15 100644 --- a/src/plugins/mcusupport/test/unittest.cpp +++ b/src/plugins/mcusupport/test/unittest.cpp @@ -1703,31 +1703,43 @@ void McuSupportTest::test_addToSystemPathFlag() void McuSupportTest::test_processWildcards_data() { QTest::addColumn("package_label"); - QTest::addColumn("path"); - QTest::addColumn("isFile"); + QTest::addColumn("expected_path"); + QTest::addColumn("paths"); - QTest::newRow("\"*\" at the end") << "FAKE_WILDCARD_TEST_1" - << "folder-123" << false; - QTest::newRow("\"*\" in the middle") << "FAKE_WILDCARD_TEST_2" - << "file-123.exe" << true; - QTest::newRow("\"*\" at the start") << "FAKE_WILDCARD_TEST_3" - << "123-file.exe" << true; + QTest::newRow("wildcard_at_the_end") << "FAKE_WILDCARD_TEST_1" << "folder-123" << QStringList {"folder-123/" }; + QTest::newRow("wildcard_in_th_middle") << "FAKE_WILDCARD_TEST_2" << "file-123.exe" << QStringList {"file-123.exe"}; + QTest::newRow("wildcard_at_the_end") << "FAKE_WILDCARD_TEST_3" << "123-file.exe" << QStringList( "123-file.exe"); + QTest::newRow("multi_wildcards") + << "FAKE_WILDCARD_TEST_MULTI" + << "2019/Community/VC/Tools/MSVC/14.29.30133/bin/Hostx64/x64" + << QStringList{ + "2019/Community/VC/Tools/MSVC/14.29.30133/bin/Hostx64/x64/", + "2019/Alpha/Beta/Gamma/", + "2019/Community/VC/Tools/MSVC/", + "2019/Community/VC/Tools/MSVC/14.29.30133/bin/", + "2019/Community/VC/Tools/MSVC/14.29.30133/bin/Hostx64/", + "2019/Enterprise/VC/Tools/MSVC/", + }; } void McuSupportTest::test_processWildcards() { QFETCH(QString, package_label); - QFETCH(QString, path); - QFETCH(bool, isFile); + QFETCH(QString, expected_path); + QFETCH(QStringList, paths); - QVERIFY(createFakePath(testing_output_dir / "wildcards" / path, isFile)); + for (const auto &path : paths) + QVERIFY(createFakePath(testing_output_dir / "wildcards" / path, !path.endsWith("/"))); auto [targets, packages] = createTestingKitTargetsAndPackages(wildcards_test_kit); auto testWildcardsPackage = findOrDefault(packages, [&](const McuPackagePtr &pkg) { return (pkg->label() == package_label); }); QVERIFY(testWildcardsPackage != nullptr); - QCOMPARE(testWildcardsPackage->path().toString(), FilePath(testing_output_dir / "wildcards" / path).toString()); + QVERIFY(paths.size() > 0); + // FilePaths with "/" at the end and without it evaluate to different paths. + QCOMPARE(testWildcardsPackage->path(), + FilePath(testing_output_dir / "wildcards" / expected_path)); } void McuSupportTest::test_nonemptyVersionDetector() diff --git a/src/plugins/mcusupport/test/wildcards_test_kit_json.h b/src/plugins/mcusupport/test/wildcards_test_kit_json.h index 6737c39f56c..4efda64e42f 100644 --- a/src/plugins/mcusupport/test/wildcards_test_kit_json.h +++ b/src/plugins/mcusupport/test/wildcards_test_kit_json.h @@ -34,6 +34,13 @@ constexpr auto wildcards_test_kit = R"( "defaultValue": "%{MCU_TESTING_FOLDER}/wildcards/*-file.exe", "envVar": "", "type": "path" + }, + { + "label": "FAKE_WILDCARD_TEST_MULTI", + "description": "Assert '*' is replaced by possible values", + "defaultValue": "%{MCU_TESTING_FOLDER}/wildcards/2019/*/VC/Tools/MSVC/*/bin/Hostx64/x64", + "envVar": "", + "type": "path" } ] },