diff --git a/.github/workflows/build_cmake.yml b/.github/workflows/build_cmake.yml index 52c2a0e4d29..3b2c1755b2f 100644 --- a/.github/workflows/build_cmake.yml +++ b/.github/workflows/build_cmake.yml @@ -9,7 +9,7 @@ on: env: QT_VERSION: 6.5.2 MACOS_DEPLOYMENT_TARGET: 10.15 - CLANG_VERSION: 17.0.0-rc4 + CLANG_VERSION: 17.0.1 ELFUTILS_VERSION: 0.175 CMAKE_VERSION: 3.21.1 NINJA_VERSION: 1.10.2 diff --git a/coin/instructions/common_environment.yaml b/coin/instructions/common_environment.yaml index 2a56fe600e9..ea72589c1df 100644 --- a/coin/instructions/common_environment.yaml +++ b/coin/instructions/common_environment.yaml @@ -7,7 +7,7 @@ instructions: variableValue: "RelWithDebInfo" - type: EnvironmentVariable variableName: LLVM_BASE_URL - variableValue: https://ci-files02-hki.ci.qt.io/packages/jenkins/qtcreator_libclang/libclang-release_17.0.0-rc4-based + variableValue: https://ci-files02-hki.ci.qt.io/packages/jenkins/qtcreator_libclang/libclang-release_17.0.1-based - type: EnvironmentVariable variableName: QTC_QT_BASE_URL variableValue: "https://ci-files02-hki.ci.qt.io/packages/jenkins/archive/qt/6.5/6.5.2-released/Qt" diff --git a/share/qtcreator/debugger/lldbbridge.py b/share/qtcreator/debugger/lldbbridge.py index df1f57dc1c4..8c25b90c380 100644 --- a/share/qtcreator/debugger/lldbbridge.py +++ b/share/qtcreator/debugger/lldbbridge.py @@ -2068,20 +2068,23 @@ class Tester(Dumper): lldb.SBDebugger.Destroy(self.debugger) +if 'QT_CREATOR_LLDB_PROCESS' in os.environ: + # Initialize Qt Creator dumper + try: + theDumper = Dumper() + except Exception as error: + print('@\nstate="enginesetupfailed",error="{}"@\n'.format(error)) + # ------------------------------ For use in LLDB ------------------------------ +debug = print if 'QT_LLDB_SUMMARY_PROVIDER_DEBUG' in os.environ \ + else lambda *a, **k: None -from pprint import pprint - -__module__ = sys.modules[__name__] -DEBUG = False if not hasattr(__module__, 'DEBUG') else DEBUG - +debug(f"Loading lldbbridge.py from {__file__}") class LogMixin(): @staticmethod def log(message='', log_caller=False, frame=1, args=''): - if not DEBUG: - return if log_caller: message = ": " + message if len(message) else '' # FIXME: Compute based on first frame not in this class? @@ -2090,7 +2093,7 @@ class LogMixin(): localz = frame.f_locals instance = str(localz["self"]) + "." if 'self' in localz else '' message = "%s%s(%s)%s" % (instance, fn, args, message) - print(message) + debug(message) @staticmethod def log_fn(arg_str=''): @@ -2437,6 +2440,11 @@ class SyntheticChildrenProvider(SummaryProvider): def __lldb_init_module(debugger, internal_dict): # Module is being imported in an LLDB session + if 'QT_CREATOR_LLDB_PROCESS' in os.environ: + # Let Qt Creator take care of its own dumper + return + + debug("Initializing module with", debugger) if not __name__ == 'qt': # Make available under global 'qt' name for consistency, @@ -2471,10 +2479,3 @@ def __lldb_init_module(debugger, internal_dict): % ("qt.SyntheticChildrenProvider", type_category)) debugger.HandleCommand('type category enable %s' % type_category) - - -if __name__ == "lldbbridge": - try: - theDumper = Dumper() - except Exception as error: - print('@\nstate="enginesetupfailed",error="{}"@\n'.format(error)) diff --git a/share/qtcreator/templates/wizards/autotest/wizard.json b/share/qtcreator/templates/wizards/autotest/boosttest/wizard.json similarity index 50% rename from share/qtcreator/templates/wizards/autotest/wizard.json rename to share/qtcreator/templates/wizards/autotest/boosttest/wizard.json index 71dd8de4b7a..4b674389bc4 100644 --- a/share/qtcreator/templates/wizards/autotest/wizard.json +++ b/share/qtcreator/templates/wizards/autotest/boosttest/wizard.json @@ -1,12 +1,12 @@ { "version": 1, "supportedProjectTypes": [ "CMakeProjectManager.CMakeProject", "Qbs.QbsProject", "Qt4ProjectManager.Qt4Project" ], - "id": "R.AutoTest", - "category": "H.Project", - "trDescription": "Creates a new unit test project. Unit tests allow you to verify that the code is fit for use and that there are no regressions.", - "trDisplayName": "Auto Test Project", - "trDisplayCategory": "Other Project", - "icon": "autotest.png", + "id": "M.BoostAutoTest", + "category": "I.TestProject", + "trDescription": "Creates a new unit test project using Boost. Unit tests allow you to verify that the code is fit for use and that there are no regressions.", + "trDisplayName": "Boost Test Project", + "trDisplayCategory": "Test Project", + "icon": "../autotest.png", "iconKind": "Themed", "featuresRequired": [ "QtSupport.Wizards.FeatureDesktop" ], "enabled": "%{JS: value('Plugins').indexOf('CppEditor') >= 0}", @@ -33,10 +33,6 @@ { "key": "MainCppName", "value": "%{JS: 'main.' + Util.preferredSuffix('text/x-c++src') }" }, - { - "key": "TestCaseFileGTestWithCppSuffix", - "value": "%{JS: 'tst_' + value('TestCaseName').toLowerCase() + '.' + Util.preferredSuffix('text/x-c++src') }" - }, { "key": "GUARD", "value": "%{JS: value('TestCaseFileWithHeaderSuffix').toUpperCase().replace('.', '_') }" @@ -44,10 +40,6 @@ { "key": "TestCaseFileWithCppSuffix", "value": "%{JS: 'tst_' + value('TestCaseName').toLowerCase() + '.' + Util.preferredSuffix('text/x-c++src') }" - }, - { - "key": "TestCaseFileWithQmlSuffix", - "value": "%{JS: 'tst_' + value('TestCaseName').toLowerCase() + '.qml' }" } ], @@ -59,7 +51,7 @@ "typeId": "Project", "data": { - "trDescription": "This wizard creates a simple unit test project." + "trDescription": "This wizard creates a simple unit test project using Boost." } }, { @@ -77,18 +69,6 @@ "index": 0, "items": [ - { - "trKey": "Qt Test", - "value": "QtTest" - }, - { - "trKey": "Google Test", - "value": "GTest" - }, - { - "trKey": "Qt Quick Test", - "value": "QtQuickTest" - }, { "trKey": "Boost Test (header only)", "value": "BoostTest" @@ -96,28 +76,13 @@ { "trKey": "Boost Test (shared libraries)", "value": "BoostTest_dyn" - }, - { - "trKey": "Catch2", - "value": "Catch2" } - ] } }, - { - "name": "RequireGUI", - "trDisplayName": "GUI Application", - "visible": "%{JS: value('TestFrameWork') === 'QtTest'}", - "type": "CheckBox", - "data": { - "checked": false - } - }, { "name": "TestSuiteName", "trDisplayName": "Test suite name:", - "visible": "%{JS: ['BoostTest', 'BoostTest_dyn', 'GTest'].indexOf(value('TestFrameWork')) >= 0}", "mandatory": true, "type": "LineEdit", "data": { "validator": "^[a-zA-Z_0-9]+$" } @@ -129,43 +94,6 @@ "type": "LineEdit", "data": { "validator": "^[a-zA-Z_0-9]+$" } }, - { - "name": "RequireApplication", - "trDisplayName": "Requires QApplication", - "visible": "%{JS: value('TestFrameWork') === 'QtTest'}", - "type": "CheckBox", - "data": { - "checked": false - } - }, - { - "name": "UseSetupCode", - "trDisplayName": "Generate setup code", - "visible": "%{JS: value('TestFrameWork') === 'QtQuickTest'}", - "type": "CheckBox", - "data": { - "checked": false - } - }, - { - "name": "GenerateInitAndCleanup", - "trDisplayName": "Generate initialization and cleanup code", - "visible": "%{JS: [ 'QtTest', 'QtQuickTest' ].indexOf(value('TestFrameWork')) >= 0 }", - "type": "CheckBox", - "data": { - "checked": false - } - }, - { - "name": "GTestRepository", - "trDisplayName": "Googletest source directory (optional):", - "visible": "%{JS: value('TestFrameWork') === 'GTest'}", - "mandatory": false, - "type": "PathChooser", - "data": { - "kind": "existingDirectory" - } - }, { "name": "BoostIncDir", "trDisplayName": "Boost include directory (optional):", @@ -186,25 +114,6 @@ "kind": "existingDirectory" } }, - { - "name": "CatchIncDir", - "trDisplayName": "Catch2 include directory (optional):", - "visible": "%{JS: value('TestFrameWork') === 'Catch2'}", - "mandatory": false, - "type": "PathChooser", - "data": { - "kind": "existingDirectory" - } - }, - { - "name": "Catch2NeedsQt", - "trDisplayName": "Use Qt libraries", - "visible": "%{JS: '%{TestFrameWork}' === 'Catch2'}", - "type": "CheckBox", - "data": { - "checked": true - } - }, { "name": "BuildSystem", "trDisplayName": "Build system:", @@ -242,7 +151,7 @@ "enabled": "%{IsTopLevelProject}", "data": { "projectFilePath": "%{ProjectFilePath}", - "requiredFeatures": [ "%{JS: (value('TestFrameWork') === 'QtQuickTest' ? 'QtSupport.Wizards.FeatureQtQuick.2' : ((value('BuildSystem') === 'qmake' || value('TestFrameWork') === 'QtTest') ? 'QtSupport.Wizards.FeatureQt' : 'QtSupport.Wizards.FeatureDesktop' )) }" ] + "requiredFeatures": [ "%{JS: value('BuildSystem') === 'qmake' ? 'QtSupport.Wizards.FeatureQt' : 'QtSupport.Wizards.FeatureDesktop' }" ] } }, { @@ -258,87 +167,38 @@ "data": [ { - "source": "files/gtest_dependency.pri", - "target": "gtest_dependency.pri", - "condition": "%{JS: value('TestFrameWork') == 'GTest' && value('BuildSystem') == 'qmake'}", - "openInEditor": false - }, - { - "source": "files/googlecommon.js", - "target": "googlecommon.js", - "condition": "%{JS: value('TestFrameWork') == 'GTest' && value('BuildSystem') == 'qbs'}", - "openInEditor": false - }, - { - "source": "files/tst.pro", + "source": "../files/tst.pro", "target": "%{ProjectFilePath}", "condition": "%{JS: value('BuildSystem') == 'qmake'}", "openInEditor": false, "openAsProject": true }, { - "source": "files/tst.qbs", + "source": "../files/tst.qbs", "target": "%{ProjectFilePath}", "condition": "%{JS: value('BuildSystem') == 'qbs'}", "openInEditor": false, "openAsProject": true }, { - "source": "files/tst.txt", + "source": "../files/tst.txt", "target": "CMakeLists.txt", "condition": "%{JS: value('BuildSystem') == 'cmake'}", "openInEditor": false, "openAsProject": true }, { - "source": "files/tst_src_gt.cpp", - "target": "%{TestCaseFileGTestWithCppSuffix}", - "condition": "%{JS: value('TestFrameWork') == 'GTest'}", - "openInEditor": true - }, - { - "source": "files/tst_src.cpp", - "target": "%{TestCaseFileWithCppSuffix}", - "condition": "%{JS: value('TestFrameWork') == 'QtTest'}", - "openInEditor": true - }, - { - "source": "files/tst_main.cpp", + "source": "../files/tst_main.cpp", "target": "%{MainCppName}", - "condition": "%{JS: ['GTest', 'QtQuickTest', 'BoostTest', 'BoostTest_dyn', 'Catch2'].indexOf(value('TestFrameWork')) >= 0}", "openInEditor": true }, { - "source": "files/tst_src_boost.cpp", + "source": "../files/tst_src_boost.cpp", "target": "%{TestCaseFileWithCppSuffix}", "condition": "%{JS: value('TestFrameWork') === 'BoostTest_dyn'}" }, { - "source": "files/tst_qml.tmpl", - "target": "%{TestCaseFileWithQmlSuffix}", - "condition": "%{JS: value('TestFrameWork') === 'QtQuickTest'}", - "openInEditor": true - }, - { - "source": "files/setup.cpp", - "target": "setup.cpp", - "condition": "%{JS: value('TestFrameWork') === 'QtQuickTest'}", - "openInEditor": true - }, - { - "source": "files/setup.h", - "target": "setup.h", - "condition": "%{JS: value('TestFrameWork') === 'QtQuickTest'}", - "openInEditor": true - }, - { - "source": "files/catch2_tst.cpp", - "target": "%{TestCaseFileWithCppSuffix}", - "condition": "%{JS: '%{TestFrameWork}' === 'Catch2'}", - "openInEditor": true - }, - { - "source": "../projects/git.ignore", + "source": "../../projects/git.ignore", "target": ".gitignore", "condition": "%{JS: ( %{IsTopLevelProject} && value('VersionControl') === 'G.Git' )}" } diff --git a/share/qtcreator/templates/wizards/autotest/catch/wizard.json b/share/qtcreator/templates/wizards/autotest/catch/wizard.json new file mode 100644 index 00000000000..d9ca160459a --- /dev/null +++ b/share/qtcreator/templates/wizards/autotest/catch/wizard.json @@ -0,0 +1,184 @@ +{ + "version": 1, + "supportedProjectTypes": [ "CMakeProjectManager.CMakeProject", "Qbs.QbsProject", "Qt4ProjectManager.Qt4Project" ], + "id": "R.CatchAutoTest", + "category": "I.TestProject", + "trDescription": "Creates a new unit test project using Catch2. Unit tests allow you to verify that the code is fit for use and that there are no regressions.", + "trDisplayName": "Catch2 Test Project", + "trDisplayCategory": "Test Project", + "icon": "../autotest.png", + "iconKind": "Themed", + "featuresRequired": [ "QtSupport.Wizards.FeatureDesktop" ], + "enabled": "%{JS: value('Plugins').indexOf('CppEditor') >= 0}", + + "options": + [ + { "key": "TestFrameWork", + "value": "Catch2" + }, + { "key": "ProjectFilePath", + "value": "%{JS: value('BuildSystem') == 'qmake' ? value('ProFileName') : (value('BuildSystem') == 'qbs' ? value('QbsFileName') : value('CMakeFileName')) }" + }, + { "key": "ProFileName", + "value": "%{JS: Util.fileName(value('ProjectDirectory') + '/' + value('ProjectName'), 'pro')}" + }, + { + "key": "QbsFileName", + "value": "%{JS: Util.fileName(value('ProjectDirectory') + '/' + value('ProjectName'), 'qbs')}" + }, + { + "key": "CMakeFileName", + "value": "%{ProjectDirectory}/CMakeLists.txt" + }, + { "key": "IsTopLevelProject", + "value": "%{JS: !'%{Exists:ProjectExplorer.Profile.Ids}' }" + }, + { "key": "MainCppName", + "value": "%{JS: 'main.' + Util.preferredSuffix('text/x-c++src') }" + }, + { + "key": "GUARD", + "value": "%{JS: value('TestCaseFileWithHeaderSuffix').toUpperCase().replace('.', '_') }" + }, + { + "key": "TestCaseFileWithCppSuffix", + "value": "%{JS: 'tst_' + value('TestCaseName').toLowerCase() + '.' + Util.preferredSuffix('text/x-c++src') }" + } + ], + + "pages": + [ + { + "trDisplayName": "Project Location", + "trShortTitle": "Location", + "typeId": "Project", + "data": + { + "trDescription": "This wizard creates a simple unit test project using Catch2." + } + }, + { + "trDisplayName": "Project and Test Information", + "trShortTitle": "Details", + "typeId": "Fields", + "data": + [ + { + "name": "TestCaseName", + "trDisplayName": "Test case name:", + "mandatory": true, + "type": "LineEdit", + "data": { "validator": "^[a-zA-Z_0-9]+$" } + }, + { + "name": "CatchIncDir", + "trDisplayName": "Catch2 include directory (optional):", + "visible": "%{JS: value('TestFrameWork') === 'Catch2'}", + "mandatory": false, + "type": "PathChooser", + "data": { + "kind": "existingDirectory" + } + }, + { + "name": "Catch2NeedsQt", + "trDisplayName": "Use Qt libraries", + "visible": "%{JS: '%{TestFrameWork}' === 'Catch2'}", + "type": "CheckBox", + "data": { + "checked": false + } + }, + { + "name": "BuildSystem", + "trDisplayName": "Build system:", + "type": "ComboBox", + "persistenceKey": "BuildSystemType", + "data": + { + "index": 1, + "items": + [ + { + "trKey": "qmake", + "value": "qmake", + "condition": "%{JS: value('Plugins').indexOf('QmakeProjectManager') >= 0}" + }, + { + "trKey": "CMake", + "value": "cmake", + "condition": "%{JS: value('Plugins').indexOf('CMakeProjectManager') >= 0}" + }, + { + "trKey": "Qbs", + "value": "qbs", + "condition": "%{JS: value('Plugins').indexOf('QbsProjectManager') >= 0}" + } + ] + } + } + ] + }, + { + "trDisplayName": "Kit Selection", + "trShortTitle": "Kits", + "typeId": "Kits", + "enabled": "%{IsTopLevelProject}", + "data": { + "projectFilePath": "%{ProjectFilePath}", + "requiredFeatures": [ "%{JS: (value('Catch2NeedsQt') || value('BuildSystem') === 'qmake') ? 'QtSupport.Wizards.FeatureQt' : 'QtSupport.Wizards.FeatureDesktop' }" ] + } + }, + { + "trDisplayName": "Project Management", + "trShortTitle": "Summary", + "typeId": "Summary" + } + ], + "generators": + [ + { + "typeId": "File", + "data": + [ + { + "source": "../files/tst.pro", + "target": "%{ProjectFilePath}", + "condition": "%{JS: value('BuildSystem') == 'qmake'}", + "openInEditor": false, + "openAsProject": true + }, + { + "source": "../files/tst.qbs", + "target": "%{ProjectFilePath}", + "condition": "%{JS: value('BuildSystem') == 'qbs'}", + "openInEditor": false, + "openAsProject": true + }, + { + "source": "../files/tst.txt", + "target": "CMakeLists.txt", + "condition": "%{JS: value('BuildSystem') == 'cmake'}", + "openInEditor": false, + "openAsProject": true + }, + { + "source": "../files/tst_main.cpp", + "target": "%{MainCppName}", + "openInEditor": true + }, + { + "source": "../files/catch2_tst.cpp", + "target": "%{TestCaseFileWithCppSuffix}", + "condition": "%{JS: '%{TestFrameWork}' === 'Catch2'}", + "openInEditor": true + }, + { + "source": "../../projects/git.ignore", + "target": ".gitignore", + "condition": "%{JS: ( %{IsTopLevelProject} && value('VersionControl') === 'G.Git' )}" + } + ] + } + ] +} diff --git a/share/qtcreator/templates/wizards/autotest/files/tst.txt b/share/qtcreator/templates/wizards/autotest/files/tst.txt index 5e486318ce5..f451cd1ba75 100644 --- a/share/qtcreator/templates/wizards/autotest/files/tst.txt +++ b/share/qtcreator/templates/wizards/autotest/files/tst.txt @@ -150,7 +150,7 @@ find_package(Boost COMPONENTS unit_test_framework REQUIRED) set(CMAKE_CXX_STANDARD 11) set(CMAKE_CXX_STANDARD_REQUIRED ON) -add_executable(%{TestCaseName} %{MainCppName} %{TestCaseFileGTestWithCppSuffix}) +add_executable(%{TestCaseName} %{MainCppName} %{TestCaseFileWithCppSuffix}) add_test(NAME %{TestCaseName} COMMAND %{TestCaseName}) if (Boost_FOUND) include_directories(${Boost_INCLUDE_DIRS}) @@ -165,7 +165,8 @@ find_package(QT NAMES Qt6 Qt5 REQUIRED COMPONENTS Gui) find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Gui) @endif -add_executable(${PROJECT_NAME} %{TestCaseFileWithCppSuffix} main.cpp) +add_executable(%{TestCaseName} %{TestCaseFileWithCppSuffix} main.cpp) +add_test(NAME %{TestCaseName} COMMAND %{TestCaseName}) @if "%{Catch2NeedsQt}" == "true" target_link_libraries(%{TestCaseName} PRIVATE Qt${QT_VERSION_MAJOR}::Gui) diff --git a/share/qtcreator/templates/wizards/autotest/gtest/wizard.json b/share/qtcreator/templates/wizards/autotest/gtest/wizard.json new file mode 100644 index 00000000000..5e79ab5539a --- /dev/null +++ b/share/qtcreator/templates/wizards/autotest/gtest/wizard.json @@ -0,0 +1,192 @@ +{ + "version": 1, + "supportedProjectTypes": [ "CMakeProjectManager.CMakeProject", "Qbs.QbsProject", "Qt4ProjectManager.Qt4Project" ], + "id": "G.AutoTest", + "category": "I.TestProject", + "trDescription": "Creates a new unit test project using Google Test. Unit tests allow you to verify that the code is fit for use and that there are no regressions.", + "trDisplayName": "Google Test Project", + "trDisplayCategory": "Test Project", + "icon": "../autotest.png", + "iconKind": "Themed", + "featuresRequired": [ "QtSupport.Wizards.FeatureDesktop" ], + "enabled": "%{JS: value('Plugins').indexOf('CppEditor') >= 0}", + + "options": + [ + { "key": "TestFrameWork", + "value": "GTest" + }, + { "key": "ProjectFilePath", + "value": "%{JS: value('BuildSystem') == 'qmake' ? value('ProFileName') : (value('BuildSystem') == 'qbs' ? value('QbsFileName') : value('CMakeFileName')) }" + }, + { "key": "ProFileName", + "value": "%{JS: Util.fileName(value('ProjectDirectory') + '/' + value('ProjectName'), 'pro')}" + }, + { + "key": "QbsFileName", + "value": "%{JS: Util.fileName(value('ProjectDirectory') + '/' + value('ProjectName'), 'qbs')}" + }, + { + "key": "CMakeFileName", + "value": "%{ProjectDirectory}/CMakeLists.txt" + }, + { "key": "IsTopLevelProject", + "value": "%{JS: !'%{Exists:ProjectExplorer.Profile.Ids}' }" + }, + { "key": "MainCppName", + "value": "%{JS: 'main.' + Util.preferredSuffix('text/x-c++src') }" + }, + { + "key": "TestCaseFileGTestWithCppSuffix", + "value": "%{JS: 'tst_' + value('TestCaseName').toLowerCase() + '.' + Util.preferredSuffix('text/x-c++src') }" + }, + { + "key": "GUARD", + "value": "%{JS: value('TestCaseFileWithHeaderSuffix').toUpperCase().replace('.', '_') }" + } + ], + + "pages": + [ + { + "trDisplayName": "Project Location", + "trShortTitle": "Location", + "typeId": "Project", + "data": + { + "trDescription": "This wizard creates a simple unit test project using Google Test." + } + }, + { + "trDisplayName": "Project and Test Information", + "trShortTitle": "Details", + "typeId": "Fields", + "data": + [ + { + "name": "TestSuiteName", + "trDisplayName": "Test suite name:", + "mandatory": true, + "type": "LineEdit", + "data": { "validator": "^[a-zA-Z_0-9]+$" } + }, + { + "name": "TestCaseName", + "trDisplayName": "Test case name:", + "mandatory": true, + "type": "LineEdit", + "data": { "validator": "^[a-zA-Z_0-9]+$" } + }, + { + "name": "GTestRepository", + "trDisplayName": "Googletest source directory (optional):", + "mandatory": false, + "type": "PathChooser", + "data": { + "kind": "existingDirectory" + } + }, + { + "name": "BuildSystem", + "trDisplayName": "Build system:", + "type": "ComboBox", + "persistenceKey": "BuildSystemType", + "data": + { + "index": 1, + "items": + [ + { + "trKey": "qmake", + "value": "qmake", + "condition": "%{JS: value('Plugins').indexOf('QmakeProjectManager') >= 0}" + }, + { + "trKey": "CMake", + "value": "cmake", + "condition": "%{JS: value('Plugins').indexOf('CMakeProjectManager') >= 0}" + }, + { + "trKey": "Qbs", + "value": "qbs", + "condition": "%{JS: value('Plugins').indexOf('QbsProjectManager') >= 0}" + } + ] + } + } + ] + }, + { + "trDisplayName": "Kit Selection", + "trShortTitle": "Kits", + "typeId": "Kits", + "enabled": "%{IsTopLevelProject}", + "data": { + "projectFilePath": "%{ProjectFilePath}", + "requiredFeatures": [ "%{JS: value('BuildSystem') === 'qmake' ? 'QtSupport.Wizards.FeatureQt' : 'QtSupport.Wizards.FeatureDesktop' }" ] + } + }, + { + "trDisplayName": "Project Management", + "trShortTitle": "Summary", + "typeId": "Summary" + } + ], + "generators": + [ + { + "typeId": "File", + "data": + [ + { + "source": "../files/gtest_dependency.pri", + "target": "gtest_dependency.pri", + "condition": "%{JS: value('BuildSystem') == 'qmake'}", + "openInEditor": false + }, + { + "source": "../files/googlecommon.js", + "target": "googlecommon.js", + "condition": "%{JS: value('BuildSystem') == 'qbs'}", + "openInEditor": false + }, + { + "source": "../files/tst.pro", + "target": "%{ProjectFilePath}", + "condition": "%{JS: value('BuildSystem') == 'qmake'}", + "openInEditor": false, + "openAsProject": true + }, + { + "source": "../files/tst.qbs", + "target": "%{ProjectFilePath}", + "condition": "%{JS: value('BuildSystem') == 'qbs'}", + "openInEditor": false, + "openAsProject": true + }, + { + "source": "../files/tst.txt", + "target": "CMakeLists.txt", + "condition": "%{JS: value('BuildSystem') == 'cmake'}", + "openInEditor": false, + "openAsProject": true + }, + { + "source": "../files/tst_src_gt.cpp", + "target": "%{TestCaseFileGTestWithCppSuffix}", + "openInEditor": true + }, + { + "source": "../files/tst_main.cpp", + "target": "%{MainCppName}", + "openInEditor": true + }, + { + "source": "../../projects/git.ignore", + "target": ".gitignore", + "condition": "%{JS: ( %{IsTopLevelProject} && value('VersionControl') === 'G.Git' )}" + } + ] + } + ] +} diff --git a/share/qtcreator/templates/wizards/autotest/qttest/wizard.json b/share/qtcreator/templates/wizards/autotest/qttest/wizard.json new file mode 100644 index 00000000000..75de005b3ad --- /dev/null +++ b/share/qtcreator/templates/wizards/autotest/qttest/wizard.json @@ -0,0 +1,180 @@ +{ + "version": 1, + "supportedProjectTypes": [ "CMakeProjectManager.CMakeProject", "Qbs.QbsProject", "Qt4ProjectManager.Qt4Project" ], + "id": "A.QtTestAutoTest", + "category": "I.TestProject", + "trDescription": "Creates a new unit test project using Qt Test. Unit tests allow you to verify that the code is fit for use and that there are no regressions.", + "trDisplayName": "Qt Test Project", + "trDisplayCategory": "Test Project", + "icon": "../autotest.png", + "iconKind": "Themed", + "featuresRequired": [ "QtSupport.Wizards.FeatureDesktop" ], + "enabled": "%{JS: value('Plugins').indexOf('CppEditor') >= 0}", + + "options": + [ + { "key": "TestFrameWork", + "value": "QtTest" + }, + { "key": "ProjectFilePath", + "value": "%{JS: value('BuildSystem') == 'qmake' ? value('ProFileName') : (value('BuildSystem') == 'qbs' ? value('QbsFileName') : value('CMakeFileName')) }" + }, + { "key": "ProFileName", + "value": "%{JS: Util.fileName(value('ProjectDirectory') + '/' + value('ProjectName'), 'pro')}" + }, + { + "key": "QbsFileName", + "value": "%{JS: Util.fileName(value('ProjectDirectory') + '/' + value('ProjectName'), 'qbs')}" + }, + { + "key": "CMakeFileName", + "value": "%{ProjectDirectory}/CMakeLists.txt" + }, + { "key": "IsTopLevelProject", + "value": "%{JS: !'%{Exists:ProjectExplorer.Profile.Ids}' }" + }, + { + "key": "GUARD", + "value": "%{JS: value('TestCaseFileWithHeaderSuffix').toUpperCase().replace('.', '_') }" + }, + { + "key": "TestCaseFileWithCppSuffix", + "value": "%{JS: 'tst_' + value('TestCaseName').toLowerCase() + '.' + Util.preferredSuffix('text/x-c++src') }" + } + ], + + "pages": + [ + { + "trDisplayName": "Project Location", + "trShortTitle": "Location", + "typeId": "Project", + "data": + { + "trDescription": "This wizard creates a simple unit test project using Qt Test." + } + }, + { + "trDisplayName": "Project and Test Information", + "trShortTitle": "Details", + "typeId": "Fields", + "data": + [ + { + "name": "TestCaseName", + "trDisplayName": "Test case name:", + "mandatory": true, + "type": "LineEdit", + "data": { "validator": "^[a-zA-Z_0-9]+$" } + }, + { + "name": "RequireApplication", + "trDisplayName": "Requires QApplication", + "type": "CheckBox", + "data": { + "checked": false + } + }, + { + "name": "RequireGUI", + "trDisplayName": "GUI Application", + "type": "CheckBox", + "data": { + "checked": false + } + }, + { + "name": "GenerateInitAndCleanup", + "trDisplayName": "Generate initialization and cleanup code", + "type": "CheckBox", + "data": { + "checked": false + } + }, + { + "name": "BuildSystem", + "trDisplayName": "Build system:", + "type": "ComboBox", + "persistenceKey": "BuildSystemType", + "data": + { + "index": 1, + "items": + [ + { + "trKey": "qmake", + "value": "qmake", + "condition": "%{JS: value('Plugins').indexOf('QmakeProjectManager') >= 0}" + }, + { + "trKey": "CMake", + "value": "cmake", + "condition": "%{JS: value('Plugins').indexOf('CMakeProjectManager') >= 0}" + }, + { + "trKey": "Qbs", + "value": "qbs", + "condition": "%{JS: value('Plugins').indexOf('QbsProjectManager') >= 0}" + } + ] + } + } + ] + }, + { + "trDisplayName": "Kit Selection", + "trShortTitle": "Kits", + "typeId": "Kits", + "enabled": "%{IsTopLevelProject}", + "data": { + "projectFilePath": "%{ProjectFilePath}", + "requiredFeatures": [ "QtSupport.Wizards.FeatureQt", "QtSupport.Wizards.FeatureDesktop" ] + } + }, + { + "trDisplayName": "Project Management", + "trShortTitle": "Summary", + "typeId": "Summary" + } + ], + "generators": + [ + { + "typeId": "File", + "data": + [ + { + "source": "../files/tst.pro", + "target": "%{ProjectFilePath}", + "condition": "%{JS: value('BuildSystem') == 'qmake'}", + "openInEditor": false, + "openAsProject": true + }, + { + "source": "../files/tst.qbs", + "target": "%{ProjectFilePath}", + "condition": "%{JS: value('BuildSystem') == 'qbs'}", + "openInEditor": false, + "openAsProject": true + }, + { + "source": "../files/tst.txt", + "target": "CMakeLists.txt", + "condition": "%{JS: value('BuildSystem') == 'cmake'}", + "openInEditor": false, + "openAsProject": true + }, + { + "source": "../files/tst_src.cpp", + "target": "%{TestCaseFileWithCppSuffix}", + "openInEditor": true + }, + { + "source": "../../projects/git.ignore", + "target": ".gitignore", + "condition": "%{JS: ( %{IsTopLevelProject} && value('VersionControl') === 'G.Git' )}" + } + ] + } + ] +} diff --git a/share/qtcreator/templates/wizards/autotest/quicktest/wizard.json b/share/qtcreator/templates/wizards/autotest/quicktest/wizard.json new file mode 100644 index 00000000000..e23fca42d98 --- /dev/null +++ b/share/qtcreator/templates/wizards/autotest/quicktest/wizard.json @@ -0,0 +1,192 @@ +{ + "version": 1, + "supportedProjectTypes": [ "CMakeProjectManager.CMakeProject", "Qbs.QbsProject", "Qt4ProjectManager.Qt4Project" ], + "id": "C.QuickAutoTest", + "category": "I.TestProject", + "trDescription": "Creates a new unit test project using Qt Quick Test. Unit tests allow you to verify that the code is fit for use and that there are no regressions.", + "trDisplayName": "Qt Quick Test Project", + "trDisplayCategory": "Test Project", + "icon": "../autotest.png", + "iconKind": "Themed", + "featuresRequired": [ "QtSupport.Wizards.FeatureDesktop" ], + "enabled": "%{JS: value('Plugins').indexOf('CppEditor') >= 0}", + + "options": + [ + { "key": "TestFrameWork", + "value": "QtQuickTest" + }, + { "key": "ProjectFilePath", + "value": "%{JS: value('BuildSystem') == 'qmake' ? value('ProFileName') : (value('BuildSystem') == 'qbs' ? value('QbsFileName') : value('CMakeFileName')) }" + }, + { "key": "ProFileName", + "value": "%{JS: Util.fileName(value('ProjectDirectory') + '/' + value('ProjectName'), 'pro')}" + }, + { + "key": "QbsFileName", + "value": "%{JS: Util.fileName(value('ProjectDirectory') + '/' + value('ProjectName'), 'qbs')}" + }, + { + "key": "CMakeFileName", + "value": "%{ProjectDirectory}/CMakeLists.txt" + }, + { "key": "IsTopLevelProject", + "value": "%{JS: !'%{Exists:ProjectExplorer.Profile.Ids}' }" + }, + { "key": "MainCppName", + "value": "%{JS: 'main.' + Util.preferredSuffix('text/x-c++src') }" + }, + { + "key": "GUARD", + "value": "%{JS: value('TestCaseFileWithHeaderSuffix').toUpperCase().replace('.', '_') }" + }, + { + "key": "TestCaseFileWithQmlSuffix", + "value": "%{JS: 'tst_' + value('TestCaseName').toLowerCase() + '.qml' }" + } + ], + + "pages": + [ + { + "trDisplayName": "Project Location", + "trShortTitle": "Location", + "typeId": "Project", + "data": + { + "trDescription": "This wizard creates a simple unit test project using Qt Quick Test." + } + }, + { + "trDisplayName": "Project and Test Information", + "trShortTitle": "Details", + "typeId": "Fields", + "data": + [ + { + "name": "TestCaseName", + "trDisplayName": "Test case name:", + "mandatory": true, + "type": "LineEdit", + "data": { "validator": "^[a-zA-Z_0-9]+$" } + }, + { + "name": "UseSetupCode", + "trDisplayName": "Generate setup code", + "type": "CheckBox", + "data": { + "checked": false + } + }, + { + "name": "GenerateInitAndCleanup", + "trDisplayName": "Generate initialization and cleanup code", + "type": "CheckBox", + "data": { + "checked": false + } + }, + { + "name": "BuildSystem", + "trDisplayName": "Build system:", + "type": "ComboBox", + "persistenceKey": "BuildSystemType", + "data": + { + "index": 1, + "items": + [ + { + "trKey": "qmake", + "value": "qmake", + "condition": "%{JS: value('Plugins').indexOf('QmakeProjectManager') >= 0}" + }, + { + "trKey": "CMake", + "value": "cmake", + "condition": "%{JS: value('Plugins').indexOf('CMakeProjectManager') >= 0}" + }, + { + "trKey": "Qbs", + "value": "qbs", + "condition": "%{JS: value('Plugins').indexOf('QbsProjectManager') >= 0}" + } + ] + } + } + ] + }, + { + "trDisplayName": "Kit Selection", + "trShortTitle": "Kits", + "typeId": "Kits", + "enabled": "%{IsTopLevelProject}", + "data": { + "projectFilePath": "%{ProjectFilePath}", + "requiredFeatures": [ "QtSupport.Wizards.FeatureQtQuick.2", "QtSupport.Wizards.FeatureDesktop" ] + } + }, + { + "trDisplayName": "Project Management", + "trShortTitle": "Summary", + "typeId": "Summary" + } + ], + "generators": + [ + { + "typeId": "File", + "data": + [ + { + "source": "../files/tst.pro", + "target": "%{ProjectFilePath}", + "condition": "%{JS: value('BuildSystem') == 'qmake'}", + "openInEditor": false, + "openAsProject": true + }, + { + "source": "../files/tst.qbs", + "target": "%{ProjectFilePath}", + "condition": "%{JS: value('BuildSystem') == 'qbs'}", + "openInEditor": false, + "openAsProject": true + }, + { + "source": "../files/tst.txt", + "target": "CMakeLists.txt", + "condition": "%{JS: value('BuildSystem') == 'cmake'}", + "openInEditor": false, + "openAsProject": true + }, + { + "source": "../files/tst_main.cpp", + "target": "%{MainCppName}", + "openInEditor": true + }, + { + "source": "../files/tst_qml.tmpl", + "target": "%{TestCaseFileWithQmlSuffix}", + "openInEditor": true + }, + { + "source": "../files/setup.cpp", + "target": "setup.cpp", + "condition": "%{JS: value('UseSetupCode')}", + "openInEditor": true + }, + { + "source": "../files/setup.h", + "target": "setup.h", + "condition": "%{JS: value('UseSetupCode')}", + "openInEditor": true + }, + { + "source": "../../projects/git.ignore", + "target": ".gitignore", + "condition": "%{JS: ( %{IsTopLevelProject} && value('VersionControl') === 'G.Git' )}" + } + ] + } + ] +} diff --git a/src/libs/utils/fancylineedit.cpp b/src/libs/utils/fancylineedit.cpp index f30fa4bd841..f1e177e0633 100644 --- a/src/libs/utils/fancylineedit.cpp +++ b/src/libs/utils/fancylineedit.cpp @@ -7,8 +7,8 @@ #include "execmenu.h" #include "historycompleter.h" #include "hostosinfo.h" +#include "icon.h" #include "qtcassert.h" -#include "utilsicons.h" #include "utilstr.h" #include @@ -621,7 +621,6 @@ QString FancyLineEdit::fixInputString(const QString &string) FancyIconButton::FancyIconButton(QWidget *parent) : QAbstractButton(parent) - , m_autoHide(false) { setCursor(Qt::ArrowCursor); setFocusPolicy(Qt::NoFocus); diff --git a/src/libs/utils/fancylineedit.h b/src/libs/utils/fancylineedit.h index 1b9cf4d5e52..a8bd9501274 100644 --- a/src/libs/utils/fancylineedit.h +++ b/src/libs/utils/fancylineedit.h @@ -45,8 +45,8 @@ protected: void keyReleaseEvent(QKeyEvent *ke) override; private: - float m_iconOpacity; - bool m_autoHide; + float m_iconOpacity = 1.0f; + bool m_autoHide = false; QIcon m_icon; }; diff --git a/src/libs/utils/projectintropage.cpp b/src/libs/utils/projectintropage.cpp index 0b1a0a63557..b710e68723b 100644 --- a/src/libs/utils/projectintropage.cpp +++ b/src/libs/utils/projectintropage.cpp @@ -79,6 +79,7 @@ ProjectIntroPage::ProjectIntroPage(QWidget *parent) : d->m_nameLineEdit = new Utils::FancyLineEdit(frame); d->m_pathChooser = new Utils::PathChooser(frame); + d->m_pathChooser->setObjectName("baseFolder"); // used by Squish d->m_pathChooser->setExpectedKind(PathChooser::Directory); d->m_pathChooser->setDisabled(d->m_forceSubProject); diff --git a/src/plugins/autotest/quick/quicktestparser.cpp b/src/plugins/autotest/quick/quicktestparser.cpp index ef0bba8cc89..017f9f4c6dd 100644 --- a/src/plugins/autotest/quick/quicktestparser.cpp +++ b/src/plugins/autotest/quick/quicktestparser.cpp @@ -261,7 +261,10 @@ bool QuickTestParser::handleQtQuickTest(QPromise &promise, return false; const FilePath cppFileName = document->filePath(); const FilePath proFile = FilePath::fromString(ppList.at(0)->projectFile); - m_mainCppFiles.insert(cppFileName, proFile); + { + QWriteLocker lock(&m_parseLock); + m_mainCppFiles.insert(cppFileName, proFile); + } const FilePath srcDir = FilePath::fromString(quickTestSrcDir(cppFileName)); if (srcDir.isEmpty()) return false; @@ -340,13 +343,13 @@ QuickTestParser::QuickTestParser(ITestFramework *framework) void QuickTestParser::init(const QSet &filesToParse, bool fullParse) { m_qmlSnapshot = QmlJSTools::Internal::ModelManager::instance()->snapshot(); + QWriteLocker lock(&m_parseLock); // should not be necessary if (!fullParse) { // in a full parse we get the correct entry points by the respective main m_proFilesForQmlFiles = QuickTestUtils::proFilesForQmlFiles(framework(), filesToParse); // get rid of cached main cpp files that are going to get processed anyhow for (const FilePath &file : filesToParse) { - if (m_mainCppFiles.contains(file)) { - m_mainCppFiles.remove(file); + if (m_mainCppFiles.remove(file) == 1) { if (m_mainCppFiles.isEmpty()) break; } @@ -355,6 +358,7 @@ void QuickTestParser::init(const QSet &filesToParse, bool fullParse) // get rid of all cached main cpp files m_mainCppFiles.clear(); } + lock.unlock(); m_checkForDerivedTests = theQtTestFramework().quickCheckForDerivedTests(); @@ -399,9 +403,10 @@ bool QuickTestParser::processDocument(QPromise &promise, return handleQtQuickTest(promise, cppdoc, framework()); } -FilePath QuickTestParser::projectFileForMainCppFile(const FilePath &fileName) const +FilePath QuickTestParser::projectFileForMainCppFile(const FilePath &fileName) { - return m_mainCppFiles.contains(fileName) ? m_mainCppFiles.value(fileName) : FilePath(); + QReadLocker lock(&m_parseLock); + return m_mainCppFiles.value(fileName); } } // namespace Autotest::Internal diff --git a/src/plugins/autotest/quick/quicktestparser.h b/src/plugins/autotest/quick/quicktestparser.h index e93fa9605f4..c22e1d480e1 100644 --- a/src/plugins/autotest/quick/quicktestparser.h +++ b/src/plugins/autotest/quick/quicktestparser.h @@ -8,6 +8,7 @@ #include #include +#include namespace Autotest { namespace Internal { @@ -28,7 +29,7 @@ public: void release() override; bool processDocument(QPromise &promise, const Utils::FilePath &fileName) override; - Utils::FilePath projectFileForMainCppFile(const Utils::FilePath &fileName) const; + Utils::FilePath projectFileForMainCppFile(const Utils::FilePath &fileName); QStringList supportedExtensions() const override { return {"qml"}; }; private: @@ -45,6 +46,7 @@ private: QMap > m_watchedFiles; QMap m_mainCppFiles; QSet m_prefilteredFiles; + QReadWriteLock m_parseLock; // guard for m_mainCppFiles bool m_checkForDerivedTests = false; }; diff --git a/src/plugins/autotest/testtreemodel.cpp b/src/plugins/autotest/testtreemodel.cpp index fb06b40a07b..85b8156ff39 100644 --- a/src/plugins/autotest/testtreemodel.cpp +++ b/src/plugins/autotest/testtreemodel.cpp @@ -611,7 +611,8 @@ void TestTreeModel::insertItemInParent(TestTreeItem *item, TestTreeItem *root, b delete item; } else { // restore former check state if available - std::optional cached = m_checkStateCache->get(item); + std::optional cached = m_checkStateCache ? m_checkStateCache->get(item) + : std::optional{}; if (cached.has_value()) item->setData(0, cached.value(), Qt::CheckStateRole); else diff --git a/src/plugins/cmakeprojectmanager/3rdparty/rstparser/rstparser-test.cc b/src/plugins/cmakeprojectmanager/3rdparty/rstparser/rstparser-test.cc index c70fc67c6da..70b9a6df337 100644 --- a/src/plugins/cmakeprojectmanager/3rdparty/rstparser/rstparser-test.cc +++ b/src/plugins/cmakeprojectmanager/3rdparty/rstparser/rstparser-test.cc @@ -48,6 +48,27 @@ class TestHandler : public rst::ContentHandler { void StartBlock(rst::BlockType type) { std::string tag; switch (type) { + case rst::REFERENCE_LINK: + // not used, HandleReferenceLink is used instead + break; + case rst::H1: + tag = "h1"; + break; + case rst::H2: + tag = "h2"; + break; + case rst::H3: + tag = "h3"; + break; + case rst::H4: + tag = "h4"; + break; + case rst::H5: + tag = "h5"; + break; + case rst::CODE: + tag = "code"; + break; case rst::PARAGRAPH: tag = "p"; break; @@ -80,8 +101,12 @@ class TestHandler : public rst::ContentHandler { content_.append(text, size); } - void HandleDirective(const char *type) { - content_ += std::string("<") + type + " />"; + void HandleDirective(const std::string &type, const std::string &name) { + content_ += std::string("
" + type + "
"; + } + + void HandleReferenceLink(const std::string &type, const std::string &text) { + content_ += std::string("" + text + ""; } }; @@ -93,6 +118,14 @@ std::string Parse(const char *s) { } } +TEST(ParserTest, HX) { + EXPECT_EQ("

test

", Parse("====\ntest\n====")); + EXPECT_EQ("

test

", Parse("test\n====")); + EXPECT_EQ("

test

", Parse("test\n----")); + EXPECT_EQ("

test

", Parse("test\n^^^^")); + EXPECT_EQ("
test
", Parse("test\n\"\"\"\"")); +} + TEST(ParserTest, Paragraph) { EXPECT_EQ("

test

", Parse("test")); EXPECT_EQ("

test

", Parse("\ntest")); @@ -143,6 +176,14 @@ TEST(ParserTest, Literal) { EXPECT_EQ("

::\nabc\ndef

", Parse("::\nabc\ndef")); } +TEST(ParserTest, InlineCode) { + EXPECT_EQ("

code

", Parse("``code``")); + EXPECT_EQ("

`code``

", Parse("`code``")); + EXPECT_EQ("

some code

", Parse("some ``code``")); + EXPECT_EQ("

code some

", Parse("``code`` some")); + EXPECT_EQ("

some code and more

", Parse("some ``code`` and more")); +} + TEST(ParserTest, Comment) { EXPECT_EQ("", Parse("..")); EXPECT_EQ("", Parse("..\n")); @@ -151,11 +192,49 @@ TEST(ParserTest, Comment) { } TEST(ParserTest, Directive) { - EXPECT_EQ("", Parse(".. test::")); - EXPECT_EQ("", Parse(".. test::")); - EXPECT_EQ("", Parse("..\ttest::")); + EXPECT_EQ("
test
", Parse(".. test::")); + EXPECT_EQ("
test
", Parse(".. test:: name")); + EXPECT_EQ("
test
", Parse(".. test::")); + EXPECT_EQ("
test
", Parse("..\ttest::")); + + EXPECT_EQ("
|from-text| replace
", Parse(".. |from-text| replace:: to-text")); + + std::string rst = +R"(.. code-block:: c++ + int main() { + if (false) + return 1; + return 0; + })"; + + std::string html = +R"(
code-block
int main() { + if (false) + return 1; + return 0; +}
)"; + + EXPECT_EQ(html, Parse(rst.c_str())); + + rst = +R"(.. note:: This is a cool + note. Such a cool note.)"; + + html = +R"(
note
This is a cool + note. Such a cool note.
)"; + + EXPECT_EQ(html, Parse(rst.c_str())); } +TEST(ParserTest, ReferenceLinks) { + EXPECT_EQ("

info

", Parse(":ref:`info`")); + EXPECT_EQ("

some info

", Parse("some :ref:`info`")); + EXPECT_EQ("

some info and more

", Parse("some :ref:`info` and more")); + EXPECT_EQ("

info.

", Parse(":ref:`info`.")); +} + + int main(int argc, char **argv) { #ifdef _WIN32 // Disable message boxes on assertion failures. diff --git a/src/plugins/cmakeprojectmanager/3rdparty/rstparser/rstparser.cc b/src/plugins/cmakeprojectmanager/3rdparty/rstparser/rstparser.cc index 528c572f683..f430c40e95d 100644 --- a/src/plugins/cmakeprojectmanager/3rdparty/rstparser/rstparser.cc +++ b/src/plugins/cmakeprojectmanager/3rdparty/rstparser/rstparser.cc @@ -27,6 +27,7 @@ #include "rstparser.h" +#include #include #include @@ -55,15 +56,15 @@ void rst::Parser::SkipSpace() { std::string rst::Parser::ParseDirectiveType() { const char *s = ptr_; - if (!std::isalnum(*s)) + if (!std::isalnum(*s) && *s != '|') return std::string(); for (;;) { ++s; if (std::isalnum(*s)) continue; switch (*s) { - case '-': case '_': case '+': case ':': case '.': - if (std::isalnum(s[1])) { + case '-': case '_': case '+': case ':': case '.': case '|': + if (std::isalnum(s[1]) || (*s == '|' && IsSpace(s[1]))) { ++s; continue; } @@ -91,13 +92,28 @@ void rst::Parser::EnterBlock(rst::BlockType &prev_type, rst::BlockType type) { void rst::Parser::ParseBlock( rst::BlockType type, rst::BlockType &prev_type, int indent) { std::string text; + + struct InlineTags { + rst::BlockType type; + std::size_t pos {}; + std::string text; + std::string type_string; + }; + std::vector inline_tags; + + bool have_h1 = false; for (bool first = true; ; first = false) { const char *line_start = ptr_; if (!first) { // Check indentation. SkipSpace(); - if (ptr_ - line_start != indent) + const int new_indent = ptr_ - line_start; + if (new_indent < indent) break; + // Restore the indent + if (new_indent > indent) + std::advance(ptr_, indent - new_indent); + if (*ptr_ == '\n') { ++ptr_; break; // Empty line ends the block. @@ -119,9 +135,17 @@ void rst::Parser::ParseBlock( // Copy text converting all whitespace characters to spaces. text.reserve(end - line_start + 1); - if (!first) + if (!first && !have_h1) text.push_back('\n'); enum {TAB_WIDTH = 8}; + + // Used the sections mapping from https://docs.anaconda.com/restructuredtext/index.html + struct { + BlockType type; + int count = 0; + char c = 0; + } hx[] = { {H1, 0, '=' }, {H2, 0, '='}, {H3, 0, '-'}, {H4, 0, '^'}, {H5, 0, '\"'}}; + for (const char *s = line_start; s != end; ++s) { char c = *s; if (c == '\t') { @@ -129,10 +153,60 @@ void rst::Parser::ParseBlock( TAB_WIDTH - ((indent + s - line_start) % TAB_WIDTH)); } else if (IsSpace(c)) { text.push_back(' '); + } else if (c == hx[0].c) { + ++hx[0].count; + ++hx[1].count; + } else if (c == hx[2].c) { + ++hx[2].count; + } else if (c == hx[3].c) { + ++hx[3].count; + } else if (c == hx[4].c) { + ++hx[4].count; + } else if (c == '`') { + std::string code_tag_text; + if (ParseCode(s, end - s, code_tag_text)) { + InlineTags code; + code.type = rst::CODE; + code.pos = text.size(); + code.text = code_tag_text; + inline_tags.push_back(code); + const int tag_size = 4; + s = s + code_tag_text.size() + tag_size - 1; + } else { + text.push_back(*s); + } + } else if (c == ':') { + std::string link_type; + std::string link_text; + if (ParseReferenceLink(s, end - s, link_type, link_text)) { + InlineTags link; + link.type = rst::REFERENCE_LINK; + link.pos = text.size(); + link.text = link_text; + link.type_string = link_type; + inline_tags.push_back(link); + const int tag_size = 4; + s = s + link_type.size() + link_text.size() + tag_size - 1; + } else { + text.push_back(*s); + } } else { text.push_back(*s); } } + + for (int i = 0; i < 5; ++i) { + if (hx[i].count > 0 && hx[i].count == end - line_start) { + // h1 and h2 have the same underline character + // only if there was one ontop then is h1 otherwise h2 + if (i == 0 && first) + have_h1 = true; + if ((i == 0 && !have_h1) || (i == 1 && have_h1)) + continue; + type = hx[i].type; + } + } + if (*ptr_ == '\n') ++ptr_; } @@ -144,11 +218,35 @@ void rst::Parser::ParseBlock( bool literal = type == PARAGRAPH && EndsWith(text, "::"); if (!literal || text.size() != 2) { std::size_t size = text.size(); + if (size == 0 && inline_tags.size() == 0) + return; + if (literal) --size; EnterBlock(prev_type, type); handler_->StartBlock(type); - handler_->HandleText(text.c_str(), size); + + if (inline_tags.size() == 0) { + handler_->HandleText(text.c_str(), size); + } else { + std::size_t start = 0; + for (const InlineTags &in : inline_tags) { + if (in.pos > start) + handler_->HandleText(text.c_str() + start, in.pos - start); + if (in.type == rst::REFERENCE_LINK) { + handler_->HandleReferenceLink(in.type_string, in.text); + } else { + handler_->StartBlock(in.type); + handler_->HandleText(in.text.c_str(), in.text.size()); + handler_->EndBlock(); + } + start = in.pos; + } + + if (start < size) + handler_->HandleText(text.c_str() + start, size - start); + } + handler_->EndBlock(); } if (literal) { @@ -191,6 +289,58 @@ void rst::Parser::ParseLineBlock(rst::BlockType &prev_type, int indent) { handler_->EndBlock(); } +bool rst::Parser::ParseCode(const char *s, std::size_t size, std::string &code) +{ + // It requires at least four ticks ``text`` + if (s[0] != '`' || s[1] != '`') + return false; + + if (size < 4) + return false; + + std::size_t start_pos = 2; + std::size_t end_pos = 0; + for (std::size_t i = start_pos; i < size - 1; ++i) { + if (s[i] == '`' && s[i + 1] == '`') { + end_pos = i; + break; + } + } + + if (end_pos == 0) + return false; + + code.assign(s + start_pos, end_pos - start_pos); + + return true; +} + +bool rst::Parser::ParseReferenceLink(const char *s, std::size_t size, std::string &type, std::string &text) +{ + // :type:`text` + if (size < 4) + return false; + + auto start_type_tag = s + 1; + auto end_type_tag = std::find(start_type_tag, s + size, ':'); + if (end_type_tag == s + size) + return false; + + type.assign(start_type_tag, end_type_tag - start_type_tag); + + if (*(end_type_tag + 1) != '`') + return false; + + auto start_text_tag = end_type_tag + 2; + auto end_text_tag = std::find(start_text_tag, s + size, '`'); + if (end_text_tag == s + size) + return false; + + text.assign(start_text_tag, end_text_tag - start_text_tag); + + return true; +} + void rst::Parser::Parse(const char *s) { BlockType prev_type = PARAGRAPH; ptr_ = s; @@ -214,7 +364,28 @@ void rst::Parser::Parse(const char *s) { std::string type = ParseDirectiveType(); if (!type.empty() && ptr_[0] == ':' && ptr_[1] == ':') { ptr_ += 2; - handler_->HandleDirective(type.c_str()); + + const char* after_directive = ptr_; + + // Get the name of the directive + std::string name; + while (*ptr_ && *ptr_ != '\n') { + c = *ptr_++; + if (!IsSpace(c)) + name.push_back(c); + } + + // Special case for ".. note::" which can start directly after the :: + if (type == "note" && name.size() > 0) { + ptr_ = after_directive; + SkipSpace(); + handler_->HandleDirective(type, ""); + + ParseBlock(BLOCK_QUOTE, prev_type, 0); + break; + } + + handler_->HandleDirective(type, name); } // Skip everything till the end of the line. while (*ptr_ && *ptr_ != '\n') diff --git a/src/plugins/cmakeprojectmanager/3rdparty/rstparser/rstparser.h b/src/plugins/cmakeprojectmanager/3rdparty/rstparser/rstparser.h index 547f128af7c..4fabdbf46ec 100644 --- a/src/plugins/cmakeprojectmanager/3rdparty/rstparser/rstparser.h +++ b/src/plugins/cmakeprojectmanager/3rdparty/rstparser/rstparser.h @@ -35,6 +35,13 @@ namespace rst { enum BlockType { + H1, + H2, + H3, + H4, + H5, + CODE, + REFERENCE_LINK, PARAGRAPH, LINE_BLOCK, BLOCK_QUOTE, @@ -58,7 +65,10 @@ class ContentHandler { virtual void HandleText(const char *text, std::size_t size) = 0; // Receives notification of a directive. - virtual void HandleDirective(const char *type) = 0; + virtual void HandleDirective(const std::string &type, const std::string &name) = 0; + + // Receives notification of a link. + virtual void HandleReferenceLink(const std::string &type, const std::string &text) = 0; }; // A parser for a subset of reStructuredText. @@ -85,6 +95,12 @@ class Parser { // Parses a line block. void ParseLineBlock(rst::BlockType &prev_type, int indent); + // Parses inline ``code`` + bool ParseCode(const char* s, std::size_t size, std::string &code); + + // Parses :reference:`link` + bool ParseReferenceLink(const char* s, std::size_t size, std::string &type, std::string &text); + public: explicit Parser(ContentHandler *h) : handler_(h), ptr_(0) {} @@ -94,4 +110,3 @@ class Parser { } #endif // RSTPARSER_H_ - diff --git a/src/plugins/cmakeprojectmanager/CMakeLists.txt b/src/plugins/cmakeprojectmanager/CMakeLists.txt index f1989cec6c7..010f948f8a9 100644 --- a/src/plugins/cmakeprojectmanager/CMakeLists.txt +++ b/src/plugins/cmakeprojectmanager/CMakeLists.txt @@ -46,4 +46,5 @@ add_qtc_plugin(CMakeProjectManager 3rdparty/cmake/cmListFileCache.cxx 3rdparty/cmake/cmListFileLexer.cxx 3rdparty/cmake/cmListFileCache.h + 3rdparty/rstparser/rstparser.cc 3rdparty/rstparser/rstparser.h ) diff --git a/src/plugins/cmakeprojectmanager/cmakeeditor.cpp b/src/plugins/cmakeprojectmanager/cmakeeditor.cpp index 1f1e873be52..86f8823fdee 100644 --- a/src/plugins/cmakeprojectmanager/cmakeeditor.cpp +++ b/src/plugins/cmakeprojectmanager/cmakeeditor.cpp @@ -8,8 +8,6 @@ #include "cmakebuildsystem.h" #include "cmakefilecompletionassist.h" #include "cmakeindenter.h" -#include "cmakekitaspect.h" -#include "cmakeproject.h" #include "cmakeprojectconstants.h" #include "3rdparty/cmake/cmListFileCache.h" @@ -54,13 +52,7 @@ public: CMakeEditor::CMakeEditor() { - CMakeTool *tool = nullptr; - if (auto bs = ProjectTree::currentBuildSystem()) - tool = CMakeKitAspect::cmakeTool(bs->target()->kit()); - if (!tool) - tool = CMakeToolManager::defaultCMakeTool(); - - if (tool) + if (auto tool = CMakeToolManager::defaultProjectOrDefaultCMakeTool()) m_keywords = tool->keywords(); } @@ -321,6 +313,7 @@ class CMakeHoverHandler : public TextEditor::BaseHoverHandler { mutable CMakeKeywords m_keywords; QString m_helpToolTip; + QString m_contextHelp; public: const CMakeKeywords &keywords() const; @@ -333,22 +326,13 @@ public: const CMakeKeywords &CMakeHoverHandler::keywords() const { - if (m_keywords.functions.isEmpty()) { - CMakeTool *tool = nullptr; - if (auto bs = ProjectTree::currentBuildSystem()) - tool = CMakeKitAspect::cmakeTool(bs->target()->kit()); - if (!tool) - tool = CMakeToolManager::defaultCMakeTool(); - - if (tool) + if (m_keywords.functions.isEmpty()) + if (auto tool = CMakeToolManager::defaultProjectOrDefaultCMakeTool()) m_keywords = tool->keywords(); - } return m_keywords; } -QString readFirstParagraphs(const QString &element, const FilePath &helpFile); - void CMakeHoverHandler::identifyMatch(TextEditor::TextEditorWidget *editorWidget, int pos, ReportPriority report) @@ -360,24 +344,39 @@ void CMakeHoverHandler::identifyMatch(TextEditor::TextEditorWidget *editorWidget const QString word = Utils::Text::wordUnderCursor(cursor); FilePath helpFile; - for (const auto &map : {keywords().functions, - keywords().variables, - keywords().directoryProperties, - keywords().sourceProperties, - keywords().targetProperties, - keywords().testProperties, - keywords().properties, - keywords().includeStandardModules, - keywords().findModules, - keywords().policies}) { - if (map.contains(word)) { - helpFile = map.value(word); + QString helpCategory; + struct + { + const QMap ↦ + QString helpCategory; + } keywordsListMaps[] = {{keywords().functions, "command"}, + {keywords().variables, "variable"}, + {keywords().directoryProperties, "prop_dir"}, + {keywords().sourceProperties, "prop_sf"}, + {keywords().targetProperties, "prop_tgt"}, + {keywords().testProperties, "prop_test"}, + {keywords().properties, "prop_gbl"}, + {keywords().includeStandardModules, "module"}, + {keywords().findModules, "module"}, + {keywords().policies, "policy"}}; + + for (const auto &pair : keywordsListMaps) { + if (pair.map.contains(word)) { + helpFile = pair.map.value(word); + helpCategory = pair.helpCategory; break; } } m_helpToolTip.clear(); if (!helpFile.isEmpty()) - m_helpToolTip = readFirstParagraphs(word, helpFile); + m_helpToolTip = CMakeToolManager::toolTipForRstHelpFile(helpFile); + + if (auto tool = CMakeToolManager::defaultProjectOrDefaultCMakeTool()) + m_contextHelp = QString("%1/%2/%3") + .arg(tool->documentationUrl(tool->version(), + tool->qchFilePath().isEmpty()), + helpCategory, + word); setPriority(m_helpToolTip.isEmpty() ? Priority_Tooltip : Priority_None); } @@ -385,7 +384,7 @@ void CMakeHoverHandler::identifyMatch(TextEditor::TextEditorWidget *editorWidget void CMakeHoverHandler::operateTooltip(TextEditorWidget *editorWidget, const QPoint &point) { if (!m_helpToolTip.isEmpty()) - Utils::ToolTip::show(point, m_helpToolTip, Qt::MarkdownText, editorWidget); + Utils::ToolTip::show(point, m_helpToolTip, Qt::MarkdownText, editorWidget, m_contextHelp); else Utils::ToolTip::hide(); } diff --git a/src/plugins/cmakeprojectmanager/cmakefilecompletionassist.cpp b/src/plugins/cmakeprojectmanager/cmakefilecompletionassist.cpp index 84902f9d495..dbaf380d250 100644 --- a/src/plugins/cmakeprojectmanager/cmakefilecompletionassist.cpp +++ b/src/plugins/cmakeprojectmanager/cmakefilecompletionassist.cpp @@ -5,8 +5,6 @@ #include "cmakebuildsystem.h" #include "cmakebuildtarget.h" -#include "cmakekitaspect.h" -#include "cmakeproject.h" #include "cmakeprojectconstants.h" #include "cmaketool.h" #include "cmaketoolmanager.h" @@ -143,7 +141,7 @@ static int findPathStart(const AssistInterface *interface) } template -QList generateList(const T &words, const QIcon &icon) +static QList generateList(const T &words, const QIcon &icon) { return transform(words, [&icon](const QString &word) -> AssistProposalItemInterface * { AssistProposalItem *item = new AssistProposalItem(); @@ -153,26 +151,12 @@ QList generateList(const T &words, const QIcon &i }); } -QString readFirstParagraphs(const QString &element, const FilePath &helpFile) -{ - static QMap map; - if (map.contains(helpFile)) - return map.value(helpFile); - - auto content = helpFile.fileContents(1024).value_or(QByteArray()); - const QString firstParagraphs - = QString("```\n%1\n```").arg(QString::fromUtf8(content.left(content.lastIndexOf("\n")))); - - map[helpFile] = firstParagraphs; - return firstParagraphs; -} - -QList generateList(const QMap &words, - const QIcon &icon) +static QList generateList(const QMap &words, + const QIcon &icon) { struct MarkDownAssitProposalItem : public AssistProposalItem { - Qt::TextFormat detailFormat() const { return Qt::MarkdownText; } + Qt::TextFormat detailFormat() const override { return Qt::MarkdownText; } }; QList list; @@ -180,10 +164,10 @@ QList generateList(const QMap MarkDownAssitProposalItem *item = new MarkDownAssitProposalItem(); item->setText(it.key()); if (!it.value().isEmpty()) - item->setDetail(readFirstParagraphs(it.key(), it.value())); + item->setDetail(CMakeToolManager::toolTipForRstHelpFile(it.value())); item->setIcon(icon); list << item; - }; + } return list; } @@ -203,7 +187,7 @@ static int addFilePathItems(const AssistInterface *interface, const QString word = interface->textAt(startPos, interface->position() - startPos); FilePath baseDir = interface->filePath().absoluteFilePath().parentDir(); - const int lastSlashPos = word.lastIndexOf(QLatin1Char('/')); + const qsizetype lastSlashPos = word.lastIndexOf(QLatin1Char('/')); QString prefix = word; if (lastSlashPos != -1) { @@ -227,7 +211,7 @@ static int addFilePathItems(const AssistInterface *interface, return startPos; } -QPair getLocalFunctionsAndVariables(const QByteArray &content) +static QPair getLocalFunctionsAndVariables(const QByteArray &content) { cmListFile cmakeListFile; std::string errorString; @@ -258,16 +242,8 @@ IAssistProposal *CMakeFileCompletionAssist::performAsync() Project *project = nullptr; const FilePath &filePath = interface()->filePath(); if (!filePath.isEmpty() && filePath.isFile()) { - CMakeTool *cmake = nullptr; - project = static_cast(ProjectManager::projectForFile(filePath)); - if (project && project->activeTarget()) - cmake = CMakeKitAspect::cmakeTool(project->activeTarget()->kit()); - - if (!cmake) - cmake = CMakeToolManager::defaultCMakeTool(); - - if (cmake && cmake->isValid()) - keywords = cmake->keywords(); + if (auto tool = CMakeToolManager::defaultProjectOrDefaultCMakeTool()) + keywords = tool->keywords(); } QStringList buildTargets; diff --git a/src/plugins/cmakeprojectmanager/cmakeprojectmanager.qbs b/src/plugins/cmakeprojectmanager/cmakeprojectmanager.qbs index 96520f9127d..94dfa408e15 100644 --- a/src/plugins/cmakeprojectmanager/cmakeprojectmanager.qbs +++ b/src/plugins/cmakeprojectmanager/cmakeprojectmanager.qbs @@ -93,13 +93,15 @@ QtcPlugin { name: "3rdparty" cpp.includePaths: base.concat("3rdparty/cmake") - prefix: "3rdparty/cmake/" + prefix: "3rdparty/" files: [ - "cmListFileCache.cxx", - "cmListFileCache.h", - "cmListFileLexer.cxx", - "cmListFileLexer.h", - "cmStandardLexer.h", + "cmake/cmListFileCache.cxx", + "cmake/cmListFileCache.h", + "cmake/cmListFileLexer.cxx", + "cmake/cmListFileLexer.h", + "cmake/cmStandardLexer.h", + "rstparser/rstparser.cc", + "rstparser/rstparser.h" ] } } diff --git a/src/plugins/cmakeprojectmanager/cmaketoolmanager.cpp b/src/plugins/cmakeprojectmanager/cmaketoolmanager.cpp index d8978e3bc61..65a4f55aa24 100644 --- a/src/plugins/cmakeprojectmanager/cmaketoolmanager.cpp +++ b/src/plugins/cmakeprojectmanager/cmaketoolmanager.cpp @@ -8,11 +8,17 @@ #include "cmakespecificsettings.h" #include "cmaketoolsettingsaccessor.h" +#include "3rdparty/rstparser/rstparser.h" + #include #include #include +#include +#include +#include +#include #include #include #include @@ -32,6 +38,137 @@ public: Internal::CMakeToolSettingsAccessor m_accessor; }; +class HtmlHandler : public rst::ContentHandler +{ +private: + std::stack m_tags; + + QStringList m_p; + QStringList m_h3; + QStringList m_cmake_code; + + QString m_last_directive_type; + QString m_last_directive_class; + + void StartBlock(rst::BlockType type) final + { + QString tag; + switch (type) { + case rst::REFERENCE_LINK: + // not used, HandleReferenceLink is used instead + break; + case rst::H1: + tag = "h1"; + break; + case rst::H2: + tag = "h2"; + break; + case rst::H3: + tag = "h3"; + break; + case rst::H4: + tag = "h4"; + break; + case rst::H5: + tag = "h5"; + break; + case rst::CODE: + tag = "code"; + break; + case rst::PARAGRAPH: + tag = "p"; + break; + case rst::LINE_BLOCK: + tag = "pre"; + break; + case rst::BLOCK_QUOTE: + if (m_last_directive_type == "code-block" && m_last_directive_class == "cmake") + tag = "cmake-code"; + else + tag = "blockquote"; + break; + case rst::BULLET_LIST: + tag = "ul"; + break; + case rst::LIST_ITEM: + tag = "li"; + break; + case rst::LITERAL_BLOCK: + tag = "pre"; + break; + } + + if (tag == "p") + m_p.push_back(QString()); + if (tag == "h3") + m_h3.push_back(QString()); + if (tag == "cmake-code") + m_cmake_code.push_back(QString()); + + if (tag == "code" && m_tags.top() == "p") + m_p.last().append("`"); + + m_tags.push(tag); + } + + void EndBlock() final + { + // Add a new "p" collector for any `code` markup that comes afterwads + // since we are insterested only in the first paragraph. + if (m_tags.top() == "p") + m_p.push_back(QString()); + + if (m_tags.top() == "code" && !m_p.isEmpty()) { + m_tags.pop(); + + if (m_tags.size() > 0 && m_tags.top() == "p") + m_p.last().append("`"); + } else { + m_tags.pop(); + } + } + + void HandleText(const char *text, std::size_t size) final + { + if (m_last_directive_type.endsWith("replace")) + return; + + QString str = QString::fromUtf8(text, size); + + if (m_tags.top() == "h3") + m_h3.last().append(str); + if (m_tags.top() == "p") + m_p.last().append(str); + if (m_tags.top() == "cmake-code") + m_cmake_code.last().append(str); + if (m_tags.top() == "code" && !m_p.isEmpty()) + m_p.last().append(str); + } + + void HandleDirective(const std::string &type, const std::string &name) final + { + m_last_directive_type = QString::fromStdString(type); + m_last_directive_class = QString::fromStdString(name); + } + + void HandleReferenceLink(const std::string &type, const std::string &text) final + { + Q_UNUSED(type) + if (!m_p.isEmpty()) + m_p.last().append(QString::fromStdString(text)); + } + +public: + QString content() const + { + const QString title = m_h3.isEmpty() ? QString() : m_h3.first(); + const QString description = m_p.isEmpty() ? QString() : m_p.first(); + const QString cmakeCode = m_cmake_code.isEmpty() ? QString() : m_cmake_code.first(); + + return QString("### %1\n\n%2\n\n````\n%3\n````").arg(title, description, cmakeCode); + } +}; + static CMakeToolManagerPrivate *d = nullptr; CMakeToolManager *CMakeToolManager::m_instance = nullptr; @@ -108,6 +245,37 @@ void CMakeToolManager::deregisterCMakeTool(const Id &id) } } +CMakeTool *CMakeToolManager::defaultProjectOrDefaultCMakeTool() +{ + static CMakeTool *tool = nullptr; + + auto updateTool = [&] { + tool = nullptr; + if (auto bs = ProjectExplorer::ProjectTree::currentBuildSystem()) + tool = CMakeKitAspect::cmakeTool(bs->target()->kit()); + if (!tool) + tool = CMakeToolManager::defaultCMakeTool(); + }; + + if (!tool) + updateTool(); + + QObject::connect(CMakeToolManager::instance(), + &CMakeToolManager::cmakeUpdated, + CMakeToolManager::instance(), + [&]() { updateTool(); }); + QObject::connect(CMakeToolManager::instance(), + &CMakeToolManager::cmakeRemoved, + CMakeToolManager::instance(), + [&]() { updateTool(); }); + QObject::connect(CMakeToolManager::instance(), + &CMakeToolManager::defaultCMakeChanged, + CMakeToolManager::instance(), + [&]() { updateTool(); }); + + return tool; +} + CMakeTool *CMakeToolManager::defaultCMakeTool() { return findById(d->m_defaultCMake); @@ -167,6 +335,25 @@ void CMakeToolManager::updateDocumentation() Core::HelpManager::registerDocumentation(docs); } +QString CMakeToolManager::toolTipForRstHelpFile(const FilePath &helpFile) +{ + static QHash map; + if (map.contains(helpFile)) + return map.value(helpFile); + + auto content = helpFile.fileContents(1024).value_or(QByteArray()); + content.replace("\r\n", "\n"); + + HtmlHandler handler; + rst::Parser parser(&handler); + parser.Parse(content.left(content.lastIndexOf('\n'))); + + const QString tooltip = handler.content(); + + map[helpFile] = tooltip; + return tooltip; +} + QList CMakeToolManager::autoDetectCMakeForDevice(const FilePaths &searchPaths, const QString &detectionSource, QString *logMessage) diff --git a/src/plugins/cmakeprojectmanager/cmaketoolmanager.h b/src/plugins/cmakeprojectmanager/cmaketoolmanager.h index 1b5ee74c8f1..761ba22ad76 100644 --- a/src/plugins/cmakeprojectmanager/cmaketoolmanager.h +++ b/src/plugins/cmakeprojectmanager/cmaketoolmanager.h @@ -30,6 +30,8 @@ public: static bool registerCMakeTool(std::unique_ptr &&tool); static void deregisterCMakeTool(const Utils::Id &id); + static CMakeTool *defaultProjectOrDefaultCMakeTool(); + static CMakeTool *defaultCMakeTool(); static void setDefaultCMakeTool(const Utils::Id &id); static CMakeTool *findByCommand(const Utils::FilePath &command); @@ -40,6 +42,8 @@ public: static void updateDocumentation(); + static QString toolTipForRstHelpFile(const Utils::FilePath &helpFile); + public slots: QList autoDetectCMakeForDevice(const Utils::FilePaths &searchPaths, const QString &detectionSource, diff --git a/src/plugins/ctfvisualizer/ctftracemanager.cpp b/src/plugins/ctfvisualizer/ctftracemanager.cpp index 82e5b54b762..29dbed73c28 100644 --- a/src/plugins/ctfvisualizer/ctftracemanager.cpp +++ b/src/plugins/ctfvisualizer/ctftracemanager.cpp @@ -8,17 +8,11 @@ #include "ctfvisualizertr.h" #include + #include -#include -#include -#include -#include #include -#include - - namespace CtfVisualizer { namespace Internal { @@ -26,48 +20,6 @@ using json = nlohmann::json; using namespace Constants; - -class CtfJsonParserCallback -{ - -public: - - explicit CtfJsonParserCallback(CtfTraceManager *traceManager) - : m_traceManager(traceManager) - {} - - bool callback(int depth, nlohmann::json::parse_event_t event, nlohmann::json &parsed) - { - if ((event == json::parse_event_t::array_start && depth == 0) - || (event == json::parse_event_t::key && depth == 1 && parsed == json(CtfTraceEventsKey))) { - m_isInTraceArray = true; - m_traceArrayDepth = depth; - return true; - } - if (m_isInTraceArray && event == json::parse_event_t::array_end && depth == m_traceArrayDepth) { - m_isInTraceArray = false; - return false; - } - if (m_isInTraceArray && event == json::parse_event_t::object_end && depth == m_traceArrayDepth + 1) { - m_traceManager->addEvent(parsed); - return false; - } - if (m_isInTraceArray || (event == json::parse_event_t::object_start && depth == 0)) { - // keep outer object and values in trace objects: - return true; - } - // discard any objects outside of trace array: - // TODO: parse other data, e.g. stack frames - return false; - } - -protected: - CtfTraceManager *m_traceManager; - - bool m_isInTraceArray = false; - int m_traceArrayDepth = 0; -}; - CtfTraceManager::CtfTraceManager(QObject *parent, Timeline::TimelineModelAggregator *modelAggregator, CtfStatisticsModel *statisticsModel) @@ -75,7 +27,6 @@ CtfTraceManager::CtfTraceManager(QObject *parent, , m_modelAggregator(modelAggregator) , m_statisticsModel(statisticsModel) { - } qint64 CtfTraceManager::traceDuration() const @@ -142,26 +93,6 @@ void CtfTraceManager::addEvent(const json &event) } } -void CtfTraceManager::load(const QString &filename) -{ - clearAll(); - - std::ifstream file(filename.toStdString()); - if (!file.is_open()) { - QMessageBox::warning(Core::ICore::dialogParent(), - Tr::tr("CTF Visualizer"), - Tr::tr("Cannot read the CTF file.")); - return; - } - CtfJsonParserCallback ctfParser(this); - json::parser_callback_t callback = [&ctfParser](int depth, json::parse_event_t event, json &parsed) { - return ctfParser.callback(depth, event, parsed); - }; - json unusedValues = json::parse(file, callback, /*allow_exceptions*/ false); - file.close(); - updateStatistics(); -} - void CtfTraceManager::finalize() { bool userConsentToIgnoreDeepTraces = false; diff --git a/src/plugins/ctfvisualizer/ctftracemanager.h b/src/plugins/ctfvisualizer/ctftracemanager.h index d376d010b5c..29694740297 100644 --- a/src/plugins/ctfvisualizer/ctftracemanager.h +++ b/src/plugins/ctfvisualizer/ctftracemanager.h @@ -5,13 +5,11 @@ #include "json/json.hpp" #include +#include #include #include -#include -namespace Timeline { -class TimelineModelAggregator; -} +namespace Timeline { class TimelineModelAggregator; } namespace CtfVisualizer { namespace Internal { @@ -34,7 +32,6 @@ public: void addEvent(const nlohmann::json &event); - void load(const QString &filename); void finalize(); bool isEmpty() const; @@ -46,6 +43,9 @@ public: void setThreadRestriction(const QString &tid, bool restrictToThisThread); bool isRestrictedTo(const QString &tid) const; + void updateStatistics(); + void clearAll(); + signals: void detailsRequested(const QString &title); @@ -53,10 +53,6 @@ protected: void addModelForThread(const QString &threadId, const QString &processId); void addModelsToAggregator(); - void updateStatistics(); - - void clearAll(); - Timeline::TimelineModelAggregator *const m_modelAggregator; CtfStatisticsModel *const m_statisticsModel; diff --git a/src/plugins/ctfvisualizer/ctfvisualizertool.cpp b/src/plugins/ctfvisualizer/ctfvisualizertool.cpp index 22b6bd50473..6aee14e1236 100644 --- a/src/plugins/ctfvisualizer/ctfvisualizertool.cpp +++ b/src/plugins/ctfvisualizer/ctfvisualizertool.cpp @@ -13,30 +13,29 @@ #include #include #include -#include +#include + #include +#include #include #include -#include -#include #include -#include #include #include -#include + +#include using namespace Core; using namespace CtfVisualizer::Constants; - +using namespace Utils; namespace CtfVisualizer { namespace Internal { CtfVisualizerTool::CtfVisualizerTool() : QObject (nullptr) - , m_isLoading(false) , m_loadJson(nullptr) , m_traceView(nullptr) , m_modelAggregator(new Timeline::TimelineModelAggregator(this)) @@ -150,34 +149,84 @@ Timeline::TimelineZoomControl *CtfVisualizerTool::zoomControl() const return m_zoomControl.get(); } -void CtfVisualizerTool::loadJson(const QString &filename) +class CtfJsonParserFunctor { - if (m_isLoading) - return; +public: + CtfJsonParserFunctor(QPromise &promise) + : m_promise(promise) {} - if (filename.isEmpty()) { - m_isLoading = false; + bool operator()(int depth, nlohmann::json::parse_event_t event, nlohmann::json &parsed) + { + using json = nlohmann::json; + if ((event == json::parse_event_t::array_start && depth == 0) + || (event == json::parse_event_t::key && depth == 1 && parsed == json(CtfTraceEventsKey))) { + m_isInTraceArray = true; + m_traceArrayDepth = depth; + return true; + } + if (m_isInTraceArray && event == json::parse_event_t::array_end && depth == m_traceArrayDepth) { + m_isInTraceArray = false; + return false; + } + if (m_isInTraceArray && event == json::parse_event_t::object_end && depth == m_traceArrayDepth + 1) { + m_promise.addResult(parsed); + return false; + } + if (m_isInTraceArray || (event == json::parse_event_t::object_start && depth == 0)) { + // keep outer object and values in trace objects: + return true; + } + // discard any objects outside of trace array: + // TODO: parse other data, e.g. stack frames + return false; + } + +protected: + QPromise &m_promise; + bool m_isInTraceArray = false; + int m_traceArrayDepth = 0; +}; + +static void load(QPromise &promise, const QString &fileName) +{ + using json = nlohmann::json; + + std::ifstream file(fileName.toStdString()); + if (!file.is_open()) { + promise.future().cancel(); return; } - m_isLoading = true; + CtfJsonParserFunctor functor(promise); + json::parser_callback_t callback = [&functor](int depth, json::parse_event_t event, json &parsed) { + return functor(depth, event, parsed); + }; - auto *futureInterface = new QFutureInterface(); - auto *task = new QFuture(futureInterface); + try { + json unusedValues = json::parse(file, callback, /*allow_exceptions*/ false); + } catch (...) { + // nlohmann::json can throw exceptions when requesting type that is wrong + } - QThread *thread = QThread::create([this, filename, futureInterface]() { - try { - m_traceManager->load(filename); - } catch (...) { - // nlohmann::json can throw exceptions when requesting type that is wrong - } - m_modelAggregator->moveToThread(QApplication::instance()->thread()); - m_modelAggregator->setParent(this); - futureInterface->reportFinished(); - }); + file.close(); +} - connect(thread, &QThread::finished, this, [this, thread, task, futureInterface]() { - // in main thread: +void CtfVisualizerTool::loadJson(const QString &fileName) +{ + using namespace Tasking; + + if (m_loader || fileName.isEmpty()) + return; + + const auto onSetup = [this, fileName](Async &async) { + m_traceManager->clearAll(); + async.setConcurrentCallData(load, fileName); + connect(&async, &AsyncBase::resultReadyAt, this, [this, asyncPtr = &async](int index) { + m_traceManager->addEvent(asyncPtr->resultAt(index)); + }); + }; + const auto onDone = [this] { + m_traceManager->updateStatistics(); if (m_traceManager->isEmpty()) { QMessageBox::warning(Core::ICore::dialogParent(), Tr::tr("CTF Visualizer"), @@ -189,17 +238,22 @@ void CtfVisualizerTool::loadJson(const QString &filename) zoomControl()->setRange(m_traceManager->traceBegin(), m_traceManager->traceEnd() + m_traceManager->traceDuration() / 20); } setAvailableThreads(m_traceManager->getSortedThreads()); - thread->deleteLater(); - delete task; - delete futureInterface; - m_isLoading = false; - }, Qt::QueuedConnection); + m_loader.release()->deleteLater(); + }; + const auto onError = [this] { + QMessageBox::warning(Core::ICore::dialogParent(), + Tr::tr("CTF Visualizer"), + Tr::tr("Cannot read the CTF file.")); + m_loader.release()->deleteLater(); + }; - m_modelAggregator->setParent(nullptr); - m_modelAggregator->moveToThread(thread); - - thread->start(); - Core::ProgressManager::addTask(*task, Tr::tr("Loading CTF File"), CtfVisualizerTaskLoadJson); + const Group recipe { AsyncTask(onSetup) }; + m_loader.reset(new TaskTree(recipe)); + connect(m_loader.get(), &TaskTree::done, this, onDone); + connect(m_loader.get(), &TaskTree::errorOccurred, this, onError); + auto progress = new TaskProgress(m_loader.get()); + progress->setDisplayName(Tr::tr("Loading CTF File")); + m_loader->start(); } } // namespace Internal diff --git a/src/plugins/ctfvisualizer/ctfvisualizertool.h b/src/plugins/ctfvisualizer/ctfvisualizertool.h index baea670d5cf..00be211efac 100644 --- a/src/plugins/ctfvisualizer/ctfvisualizertool.h +++ b/src/plugins/ctfvisualizer/ctfvisualizertool.h @@ -6,12 +6,15 @@ #include "ctfvisualizerconstants.h" #include + #include #include #include #include +namespace Tasking { class TaskTree; } + namespace CtfVisualizer { namespace Internal { @@ -21,7 +24,6 @@ class CtfStatisticsView; class CtfTimelineModel; class CtfVisualizerTraceView; - class CtfVisualizerTool : public QObject { Q_OBJECT @@ -34,7 +36,7 @@ public: CtfTraceManager *traceManager() const; Timeline::TimelineZoomControl *zoomControl() const; - void loadJson(const QString &filename); + void loadJson(const QString &fileName); private: void createViews(); @@ -45,11 +47,11 @@ private: void setAvailableThreads(const QList &threads); void toggleThreadRestriction(QAction *action); - Utils::Perspective m_perspective{Constants::CtfVisualizerPerspectiveId, + Utils::Perspective m_perspective{CtfVisualizer::Constants::CtfVisualizerPerspectiveId, QCoreApplication::translate("QtC::CtfVisualizer", "Chrome Trace Format Visualizer")}; - bool m_isLoading; + std::unique_ptr m_loader; QScopedPointer m_loadJson; CtfVisualizerTraceView *m_traceView; diff --git a/src/plugins/debugger/lldb/lldbengine.cpp b/src/plugins/debugger/lldb/lldbengine.cpp index 2d90f1e76c9..9d1717fff54 100644 --- a/src/plugins/debugger/lldb/lldbengine.cpp +++ b/src/plugins/debugger/lldb/lldbengine.cpp @@ -179,6 +179,7 @@ void LldbEngine::setupEngine() showMessage("STARTING LLDB: " + lldbCmd.toUserOutput()); Environment environment = runParameters().debugger.environment; + environment.appendOrSet("QT_CREATOR_LLDB_PROCESS", "1"); environment.appendOrSet("PYTHONUNBUFFERED", "1"); // avoid flushing problem on macOS DebuggerItem::addAndroidLldbPythonEnv(lldbCmd, environment); diff --git a/src/plugins/mesonprojectmanager/CMakeLists.txt b/src/plugins/mesonprojectmanager/CMakeLists.txt index 33fc6b25b71..9fc1f89b6f8 100644 --- a/src/plugins/mesonprojectmanager/CMakeLists.txt +++ b/src/plugins/mesonprojectmanager/CMakeLists.txt @@ -23,8 +23,6 @@ add_qtc_plugin(MesonProjectManager mesoninfoparser.h mesonoutputparser.cpp mesonoutputparser.h - mesonprocess.cpp - mesonprocess.h mesonproject.cpp mesonproject.h mesonprojectimporter.cpp diff --git a/src/plugins/mesonprojectmanager/mesonprocess.cpp b/src/plugins/mesonprojectmanager/mesonprocess.cpp deleted file mode 100644 index 2e4ba024559..00000000000 --- a/src/plugins/mesonprojectmanager/mesonprocess.cpp +++ /dev/null @@ -1,119 +0,0 @@ -// Copyright (C) 2020 Alexis Jeandet. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 - -#include "mesonprocess.h" - -#include "mesonprojectmanagertr.h" -#include "toolwrapper.h" - -#include -#include - -#include -#include - -#include -#include -#include - -#include - -using namespace Core; -using namespace Utils; - -namespace MesonProjectManager { -namespace Internal { - -static Q_LOGGING_CATEGORY(mesonProcessLog, "qtc.meson.buildsystem", QtWarningMsg); - -MesonProcess::MesonProcess() = default; -MesonProcess::~MesonProcess() = default; - -bool MesonProcess::run(const Command &command, - const Environment &env, - const QString &projectName, - bool captureStdo) -{ - if (!sanityCheck(command)) - return false; - m_stdo.clear(); - ProjectExplorer::TaskHub::clearTasks(ProjectExplorer::Constants::TASK_CATEGORY_BUILDSYSTEM); - setupProcess(command, env, projectName, captureStdo); - m_elapsed.start(); - m_process->start(); - qCDebug(mesonProcessLog()) << "Starting:" << command.toUserOutput(); - return true; -} - -void MesonProcess::handleProcessDone() -{ - if (m_process->result() != ProcessResult::FinishedWithSuccess) { - ProjectExplorer::TaskHub::addTask(ProjectExplorer::BuildSystemTask{ - ProjectExplorer::Task::TaskType::Error, m_process->exitMessage()}); - } - m_stdo = m_process->readAllRawStandardOutput(); - m_stderr = m_process->readAllRawStandardError(); - const QString elapsedTime = formatElapsedTime(m_elapsed.elapsed()); - MessageManager::writeSilently(elapsedTime); - emit finished(m_process->exitCode(), m_process->exitStatus()); -} - -void MesonProcess::setupProcess(const Command &command, const Environment &env, - const QString &projectName, bool captureStdo) -{ - if (m_process) - m_process.release()->deleteLater(); - m_process.reset(new Process); - connect(m_process.get(), &Process::done, this, &MesonProcess::handleProcessDone); - if (!captureStdo) { - connect(m_process.get(), &Process::readyReadStandardOutput, - this, &MesonProcess::processStandardOutput); - connect(m_process.get(), &Process::readyReadStandardError, - this, &MesonProcess::processStandardError); - } - - m_process->setWorkingDirectory(command.workDir()); - m_process->setEnvironment(env); - MessageManager::writeFlashing(Tr::tr("Running %1 in %2.") - .arg(command.toUserOutput(), command.workDir().toUserOutput())); - m_process->setCommand(command.cmdLine()); - m_process->setTimeoutS(10); - ProcessProgress *progress = new ProcessProgress(m_process.get()); - progress->setDisplayName(Tr::tr("Configuring \"%1\".").arg(projectName)); -} - -bool MesonProcess::sanityCheck(const Command &command) const -{ - const auto &exe = command.cmdLine().executable(); - if (!exe.exists()) { - //Should only reach this point if Meson exe is removed while a Meson project is opened - ProjectExplorer::TaskHub::addTask( - ProjectExplorer::BuildSystemTask{ProjectExplorer::Task::TaskType::Error, - Tr::tr("Executable does not exist: %1") - .arg(exe.toUserOutput())}); - return false; - } - if (!exe.toFileInfo().isExecutable()) { - ProjectExplorer::TaskHub::addTask( - ProjectExplorer::BuildSystemTask{ProjectExplorer::Task::TaskType::Error, - Tr::tr("Command is not executable: %1") - .arg(exe.toUserOutput())}); - return false; - } - return true; -} - -void MesonProcess::processStandardOutput() -{ - const auto data = m_process->readAllRawStandardOutput(); - MessageManager::writeSilently(QString::fromLocal8Bit(data)); - emit readyReadStandardOutput(data); -} - -void MesonProcess::processStandardError() -{ - MessageManager::writeSilently(QString::fromLocal8Bit(m_process->readAllRawStandardError())); -} - -} // namespace Internal -} // namespace MesonProjectManager diff --git a/src/plugins/mesonprojectmanager/mesonprocess.h b/src/plugins/mesonprojectmanager/mesonprocess.h deleted file mode 100644 index c01427c8fb9..00000000000 --- a/src/plugins/mesonprojectmanager/mesonprocess.h +++ /dev/null @@ -1,54 +0,0 @@ -// Copyright (C) 2020 Alexis Jeandet. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 - -#pragma once - -#include -#include -#include -#include - -#include - -namespace Utils { -class Environment; -class Process; -} - -namespace MesonProjectManager { -namespace Internal { - -class Command; - -class MesonProcess final : public QObject -{ - Q_OBJECT -public: - MesonProcess(); - ~MesonProcess(); - bool run(const Command &command, const Utils::Environment &env, - const QString &projectName, bool captureStdo = false); - - const QByteArray &stdOut() const { return m_stdo; } - const QByteArray &stdErr() const { return m_stderr; } -signals: - void finished(int exitCode, QProcess::ExitStatus exitStatus); - void readyReadStandardOutput(const QByteArray &data); - -private: - void handleProcessDone(); - void setupProcess(const Command &command, const Utils::Environment &env, - const QString &projectName, bool captureStdo); - bool sanityCheck(const Command &command) const; - - void processStandardOutput(); - void processStandardError(); - - std::unique_ptr m_process; - QElapsedTimer m_elapsed; - QByteArray m_stdo; - QByteArray m_stderr; -}; - -} // namespace Internal -} // namespace MesonProjectManager diff --git a/src/plugins/mesonprojectmanager/mesonprojectmanager.qbs b/src/plugins/mesonprojectmanager/mesonprojectmanager.qbs index 9167f025b20..df1c076eb60 100644 --- a/src/plugins/mesonprojectmanager/mesonprojectmanager.qbs +++ b/src/plugins/mesonprojectmanager/mesonprojectmanager.qbs @@ -49,8 +49,6 @@ Project { "mesonbuildconfiguration.h", "mesonbuildsystem.cpp", "mesonbuildsystem.h", - "mesonprocess.cpp", - "mesonprocess.h", "mesonproject.cpp", "mesonproject.h", "mesonprojectimporter.cpp", diff --git a/src/plugins/mesonprojectmanager/mesonprojectparser.cpp b/src/plugins/mesonprojectmanager/mesonprojectparser.cpp index 9de424bf282..5fb39a5f845 100644 --- a/src/plugins/mesonprojectmanager/mesonprojectparser.cpp +++ b/src/plugins/mesonprojectmanager/mesonprojectparser.cpp @@ -4,34 +4,45 @@ #include "mesonprojectparser.h" #include "mesoninfoparser.h" +#include "mesonprojectmanagertr.h" #include "mesonprojectnodes.h" #include "mesontools.h" #include "projecttree.h" +#include #include #include +#include +#include #include +#include #include - -#include -#include +#include #include +#include + +using namespace Core; +using namespace ProjectExplorer; +using namespace Utils; + namespace MesonProjectManager { namespace Internal { +static Q_LOGGING_CATEGORY(mesonProcessLog, "qtc.meson.buildsystem", QtWarningMsg); + struct CompilerArgs { QStringList args; QStringList includePaths; - ProjectExplorer::Macros macros; + Macros macros; }; -inline std::optional extractValueIfMatches(const QString &arg, - const QStringList &candidates) +static std::optional extractValueIfMatches(const QString &arg, + const QStringList &candidates) { for (const auto &flag : candidates) { if (arg.startsWith(flag)) @@ -40,22 +51,23 @@ inline std::optional extractValueIfMatches(const QString &arg, return std::nullopt; } -inline std::optional extractInclude(const QString &arg) +static std::optional extractInclude(const QString &arg) { return extractValueIfMatches(arg, {"-I", "/I", "-isystem", "-imsvc", "/imsvc"}); } -inline std::optional extractMacro(const QString &arg) + +static std::optional extractMacro(const QString &arg) { auto define = extractValueIfMatches(arg, {"-D", "/D"}); if (define) - return ProjectExplorer::Macro::fromKeyValue(define->toLatin1()); + return Macro::fromKeyValue(define->toLatin1()); auto undef = extractValueIfMatches(arg, {"-U", "/U"}); if (undef) - return ProjectExplorer::Macro(undef->toLatin1(), ProjectExplorer::MacroType::Undefine); + return Macro(undef->toLatin1(), MacroType::Undefine); return std::nullopt; } -CompilerArgs splitArgs(const QStringList &args) +static CompilerArgs splitArgs(const QStringList &args) { CompilerArgs splited; for (const QString &arg : args) { @@ -74,7 +86,7 @@ CompilerArgs splitArgs(const QStringList &args) return splited; } -QStringList toAbsolutePath(const Utils::FilePath &refPath, QStringList &pathList) +static QStringList toAbsolutePath(const FilePath &refPath, QStringList &pathList) { QStringList allAbs; std::transform(std::cbegin(pathList), @@ -86,35 +98,22 @@ QStringList toAbsolutePath(const Utils::FilePath &refPath, QStringList &pathList return allAbs; } -MesonProjectParser::MesonProjectParser(const Utils::Id &meson, - Utils::Environment env, - ProjectExplorer::Project *project) +MesonProjectParser::MesonProjectParser(const Id &meson, const Environment &env, Project *project) : m_env{env} , m_meson{meson} , m_projectName{project->displayName()} { - connect(&m_process, &MesonProcess::finished, this, &MesonProjectParser::processFinished); - connect(&m_process, - &MesonProcess::readyReadStandardOutput, - &m_outputParser, - &MesonOutputParser::readStdo); - // TODO re-think the way all BuildSystem/ProjectParser are tied // I take project info here, I also take build and src dir later from // functions args. - auto fileFinder = new Utils::FileInProjectFinder; + auto fileFinder = new FileInProjectFinder; fileFinder->setProjectDirectory(project->projectDirectory()); - fileFinder->setProjectFiles(project->files(ProjectExplorer::Project::AllFiles)); + fileFinder->setProjectFiles(project->files(Project::AllFiles)); m_outputParser.setFileFinder(fileFinder); } -void MesonProjectParser::setMesonTool(const Utils::Id &meson) -{ - m_meson = meson; -} - -bool MesonProjectParser::configure(const Utils::FilePath &sourcePath, - const Utils::FilePath &buildPath, +bool MesonProjectParser::configure(const FilePath &sourcePath, + const FilePath &buildPath, const QStringList &args) { m_introType = IntroDataType::file; @@ -126,18 +125,18 @@ bool MesonProjectParser::configure(const Utils::FilePath &sourcePath, m_pendingCommands.enqueue( std::make_tuple(MesonTools::mesonWrapper(m_meson)->regenerate(sourcePath, buildPath), false)); - return m_process.run(cmd, m_env, m_projectName); + return run(cmd, m_env, m_projectName); } -bool MesonProjectParser::wipe(const Utils::FilePath &sourcePath, - const Utils::FilePath &buildPath, +bool MesonProjectParser::wipe(const FilePath &sourcePath, + const FilePath &buildPath, const QStringList &args) { return setup(sourcePath, buildPath, args, true); } -bool MesonProjectParser::setup(const Utils::FilePath &sourcePath, - const Utils::FilePath &buildPath, +bool MesonProjectParser::setup(const FilePath &sourcePath, + const FilePath &buildPath, const QStringList &args, bool forceWipe) { @@ -149,10 +148,10 @@ bool MesonProjectParser::setup(const Utils::FilePath &sourcePath, if (forceWipe || isSetup(buildPath)) cmdArgs << "--wipe"; auto cmd = MesonTools::mesonWrapper(m_meson)->setup(sourcePath, buildPath, cmdArgs); - return m_process.run(cmd, m_env, m_projectName); + return run(cmd, m_env, m_projectName); } -bool MesonProjectParser::parse(const Utils::FilePath &sourcePath, const Utils::FilePath &buildPath) +bool MesonProjectParser::parse(const FilePath &sourcePath, const FilePath &buildPath) { m_srcDir = sourcePath; m_buildDir = buildPath; @@ -165,29 +164,29 @@ bool MesonProjectParser::parse(const Utils::FilePath &sourcePath, const Utils::F } } -bool MesonProjectParser::parse(const Utils::FilePath &sourcePath) +bool MesonProjectParser::parse(const FilePath &sourcePath) { m_srcDir = sourcePath; m_introType = IntroDataType::stdo; m_outputParser.setSourceDirectory(sourcePath); - return m_process.run(MesonTools::mesonWrapper(m_meson)->introspect(sourcePath), - m_env, - m_projectName, - true); + return run(MesonTools::mesonWrapper(m_meson)->introspect(sourcePath), + m_env, + m_projectName, + true); } -QList MesonProjectParser::appsTargets() const +QList MesonProjectParser::appsTargets() const { - QList apps; + QList apps; for (const Target &target : m_parserResult.targets) { if (target.type == Target::Type::executable) { - ProjectExplorer::BuildTargetInfo bti; + BuildTargetInfo bti; bti.displayName = target.name; bti.buildKey = Target::fullName(m_buildDir, target); bti.displayNameUniquifier = bti.buildKey; - bti.targetFilePath = Utils::FilePath::fromString(target.fileName.first()); - bti.workingDirectory = Utils::FilePath::fromString(target.fileName.first()).absolutePath(); - bti.projectFilePath = Utils::FilePath::fromString(target.definedIn); + bti.targetFilePath = FilePath::fromString(target.fileName.first()); + bti.workingDirectory = FilePath::fromString(target.fileName.first()).absolutePath(); + bti.projectFilePath = FilePath::fromString(target.definedIn); bti.usesTerminal = true; apps.append(bti); } @@ -198,8 +197,8 @@ QList MesonProjectParser::appsTargets() const bool MesonProjectParser::startParser() { m_parserFutureResult = Utils::asyncRun( - ProjectExplorer::ProjectExplorerPlugin::sharedThreadPool(), - [processOutput = m_process.stdOut(), introType = m_introType, + ProjectExplorerPlugin::sharedThreadPool(), + [processOutput = m_stdo, introType = m_introType, buildDir = m_buildDir, srcDir = m_srcDir] { if (introType == IntroDataType::file) return extractParserResults(srcDir, MesonInfoParser::parse(buildDir)); @@ -212,7 +211,7 @@ bool MesonProjectParser::startParser() } MesonProjectParser::ParserData *MesonProjectParser::extractParserResults( - const Utils::FilePath &srcDir, MesonInfoParser::Result &&parserResult) + const FilePath &srcDir, MesonInfoParser::Result &&parserResult) { auto rootNode = ProjectTree::buildTree(srcDir, parserResult.targets, @@ -220,13 +219,20 @@ MesonProjectParser::ParserData *MesonProjectParser::extractParserResults( return new ParserData{std::move(parserResult), std::move(rootNode)}; } -void MesonProjectParser::addMissingTargets(QStringList &targetList) +static void addMissingTargets(QStringList &targetList) { // Not all targets are listed in introspection data - for (const auto &target : additionalTargets()) { - if (!targetList.contains(target)) { + static const QString additionalTargets[] { + Constants::Targets::all, + Constants::Targets::clean, + Constants::Targets::install, + Constants::Targets::benchmark, + Constants::Targets::scan_build + }; + + for (const QString &target : additionalTargets) { + if (!targetList.contains(target)) targetList.append(target); - } } } @@ -245,17 +251,17 @@ void MesonProjectParser::update(const QFuture emit parsingCompleted(true); } -ProjectExplorer::RawProjectPart MesonProjectParser::buildRawPart( +RawProjectPart MesonProjectParser::buildRawPart( const Target &target, const Target::SourceGroup &sources, - const ProjectExplorer::ToolChain *cxxToolChain, - const ProjectExplorer::ToolChain *cToolChain) + const ToolChain *cxxToolChain, + const ToolChain *cToolChain) { - ProjectExplorer::RawProjectPart part; + RawProjectPart part; part.setDisplayName(target.name); part.setBuildSystemTarget(Target::fullName(m_buildDir, target)); part.setFiles(sources.sources + sources.generatedSources); - auto flags = splitArgs(sources.parameters); + CompilerArgs flags = splitArgs(sources.parameters); part.setMacros(flags.macros); part.setIncludePaths(toAbsolutePath(m_buildDir, flags.includePaths)); part.setProjectFileLocation(target.definedIn); @@ -267,30 +273,10 @@ ProjectExplorer::RawProjectPart MesonProjectParser::buildRawPart( return part; } -void MesonProjectParser::processFinished(int exitCode, QProcess::ExitStatus exitStatus) +RawProjectParts MesonProjectParser::buildProjectParts( + const ToolChain *cxxToolChain, const ToolChain *cToolChain) { - if (exitCode == 0 && exitStatus == QProcess::NormalExit) { - if (m_pendingCommands.isEmpty()) - startParser(); - else { - // see comment near m_pendingCommands declaration - std::tuple args = m_pendingCommands.dequeue(); - m_process.run(std::get<0>(args), m_env, m_projectName, std::get<1>(args)); - } - } else { - if (m_introType == IntroDataType::stdo) { - auto data = m_process.stdErr(); - Core::MessageManager::writeSilently(QString::fromLocal8Bit(data)); - m_outputParser.readStdo(data); - } - emit parsingCompleted(false); - } -} - -ProjectExplorer::RawProjectParts MesonProjectParser::buildProjectParts( - const ProjectExplorer::ToolChain *cxxToolChain, const ProjectExplorer::ToolChain *cToolChain) -{ - ProjectExplorer::RawProjectParts parts; + RawProjectParts parts; for_each_source_group(m_parserResult.targets, [&parts, &cxxToolChain, @@ -321,11 +307,111 @@ bool MesonProjectParser::matchesKit(const KitData &kit) return matches; } -bool MesonProjectParser::usesSameMesonVersion(const Utils::FilePath &buildPath) +bool MesonProjectParser::usesSameMesonVersion(const FilePath &buildPath) { auto info = MesonInfoParser::mesonInfo(buildPath); auto meson = MesonTools::mesonWrapper(m_meson); return info && meson && info->mesonVersion == meson->version(); } + + +bool MesonProjectParser::run(const Command &command, + const Environment &env, + const QString &projectName, + bool captureStdo) +{ + if (!sanityCheck(command)) + return false; + m_stdo.clear(); + TaskHub::clearTasks(ProjectExplorer::Constants::TASK_CATEGORY_BUILDSYSTEM); + setupProcess(command, env, projectName, captureStdo); + m_elapsed.start(); + m_process->start(); + qCDebug(mesonProcessLog()) << "Starting:" << command.toUserOutput(); + return true; +} + +void MesonProjectParser::handleProcessDone() +{ + if (m_process->result() != ProcessResult::FinishedWithSuccess) + TaskHub::addTask(BuildSystemTask{Task::TaskType::Error, m_process->exitMessage()}); + + m_stdo = m_process->readAllRawStandardOutput(); + m_stderr = m_process->readAllRawStandardError(); + const QString elapsedTime = formatElapsedTime(m_elapsed.elapsed()); + MessageManager::writeSilently(elapsedTime); + + if (m_process->exitCode() == 0 && m_process->exitStatus() == QProcess::NormalExit) { + if (m_pendingCommands.isEmpty()) + startParser(); + else { + // see comment near m_pendingCommands declaration + std::tuple args = m_pendingCommands.dequeue(); + run(std::get<0>(args), m_env, m_projectName, std::get<1>(args)); + } + } else { + if (m_introType == IntroDataType::stdo) { + MessageManager::writeSilently(QString::fromLocal8Bit(m_stderr)); + m_outputParser.readStdo(m_stderr); + } + emit parsingCompleted(false); + } +} + +void MesonProjectParser::setupProcess(const Command &command, const Environment &env, + const QString &projectName, bool captureStdo) +{ + if (m_process) + m_process.release()->deleteLater(); + m_process.reset(new Process); + connect(m_process.get(), &Process::done, this, &MesonProjectParser::handleProcessDone); + if (!captureStdo) { + connect(m_process.get(), &Process::readyReadStandardOutput, + this, &MesonProjectParser::processStandardOutput); + connect(m_process.get(), &Process::readyReadStandardError, + this, &MesonProjectParser::processStandardError); + } + + m_process->setWorkingDirectory(command.workDir()); + m_process->setEnvironment(env); + MessageManager::writeFlashing(Tr::tr("Running %1 in %2.") + .arg(command.toUserOutput(), command.workDir().toUserOutput())); + m_process->setCommand(command.cmdLine()); + m_process->setTimeoutS(10); + ProcessProgress *progress = new ProcessProgress(m_process.get()); + progress->setDisplayName(Tr::tr("Configuring \"%1\".").arg(projectName)); +} + +bool MesonProjectParser::sanityCheck(const Command &command) const +{ + const auto &exe = command.cmdLine().executable(); + if (!exe.exists()) { + //Should only reach this point if Meson exe is removed while a Meson project is opened + TaskHub::addTask( + BuildSystemTask{Task::TaskType::Error, + Tr::tr("Executable does not exist: %1").arg(exe.toUserOutput())}); + return false; + } + if (!exe.toFileInfo().isExecutable()) { + TaskHub::addTask( + BuildSystemTask{Task::TaskType::Error, + Tr::tr("Command is not executable: %1").arg(exe.toUserOutput())}); + return false; + } + return true; +} + +void MesonProjectParser::processStandardOutput() +{ + const auto data = m_process->readAllRawStandardOutput(); + MessageManager::writeSilently(QString::fromLocal8Bit(data)); + m_outputParser.readStdo(data); +} + +void MesonProjectParser::processStandardError() +{ + MessageManager::writeSilently(QString::fromLocal8Bit(m_process->readAllRawStandardError())); +} + } // namespace Internal } // namespace MesonProjectManager diff --git a/src/plugins/mesonprojectmanager/mesonprojectparser.h b/src/plugins/mesonprojectmanager/mesonprojectparser.h index 168b4f1448d..a027aaa4d03 100644 --- a/src/plugins/mesonprojectmanager/mesonprojectparser.h +++ b/src/plugins/mesonprojectmanager/mesonprojectparser.h @@ -6,7 +6,6 @@ #include "kitdata.h" #include "mesoninfoparser.h" #include "mesonoutputparser.h" -#include "mesonprocess.h" #include "mesonprojectnodes.h" #include "mesonwrapper.h" @@ -14,9 +13,6 @@ #include #include -#include -#include - #include #include @@ -26,6 +22,7 @@ namespace Internal { class MesonProjectParser : public QObject { Q_OBJECT + enum class IntroDataType { file, stdo }; struct ParserData { @@ -34,8 +31,10 @@ class MesonProjectParser : public QObject }; public: - MesonProjectParser(const Utils::Id &meson, Utils::Environment env, ProjectExplorer::Project* project); - void setMesonTool(const Utils::Id &meson); + MesonProjectParser(const Utils::Id &meson, + const Utils::Environment &env, + ProjectExplorer::Project *project); + bool configure(const Utils::FilePath &sourcePath, const Utils::FilePath &buildPath, const QStringList &args); @@ -49,22 +48,11 @@ public: bool parse(const Utils::FilePath &sourcePath, const Utils::FilePath &buildPath); bool parse(const Utils::FilePath &sourcePath); - Q_SIGNAL void parsingCompleted(bool success); - std::unique_ptr takeProjectNode() { return std::move(m_rootNode); } - inline const BuildOptionsList &buildOptions() const { return m_parserResult.buildOptions; }; - inline const TargetsList &targets() const { return m_parserResult.targets; } - inline const QStringList &targetsNames() const { return m_targetsNames; } - - static inline QStringList additionalTargets() - { - return QStringList{Constants::Targets::all, - Constants::Targets::clean, - Constants::Targets::install, - Constants::Targets::benchmark, - Constants::Targets::scan_build}; - } + const BuildOptionsList &buildOptions() const { return m_parserResult.buildOptions; }; + const TargetsList &targets() const { return m_parserResult.targets; } + const QStringList &targetsNames() const { return m_targetsNames; } QList appsTargets() const; @@ -72,26 +60,27 @@ public: const ProjectExplorer::ToolChain *cxxToolChain, const ProjectExplorer::ToolChain *cToolChain); - inline void setEnvironment(const Utils::Environment &environment) { m_env = environment; } + void setEnvironment(const Utils::Environment &environment) { m_env = environment; } - inline void setQtVersion(Utils::QtMajorVersion v) { m_qtVersion = v; } + void setQtVersion(Utils::QtMajorVersion v) { m_qtVersion = v; } bool matchesKit(const KitData &kit); bool usesSameMesonVersion(const Utils::FilePath &buildPath); +signals: + void parsingCompleted(bool success); + private: bool startParser(); static ParserData *extractParserResults(const Utils::FilePath &srcDir, MesonInfoParser::Result &&parserResult); - static void addMissingTargets(QStringList &targetList); void update(const QFuture &data); ProjectExplorer::RawProjectPart buildRawPart(const Target &target, const Target::SourceGroup &sources, const ProjectExplorer::ToolChain *cxxToolChain, const ProjectExplorer::ToolChain *cToolChain); - void processFinished(int exitCode, QProcess::ExitStatus exitStatus); - MesonProcess m_process; + MesonOutputParser m_outputParser; Utils::Environment m_env; Utils::Id m_meson; @@ -108,6 +97,22 @@ private: // maybe moving meson to build step could make this class simpler // also this should ease command dependencies QQueue> m_pendingCommands; + + bool run(const Command &command, const Utils::Environment &env, + const QString &projectName, bool captureStdo = false); + + void handleProcessDone(); + void setupProcess(const Command &command, const Utils::Environment &env, + const QString &projectName, bool captureStdo); + bool sanityCheck(const Command &command) const; + + void processStandardOutput(); + void processStandardError(); + + std::unique_ptr m_process; + QElapsedTimer m_elapsed; + QByteArray m_stdo; + QByteArray m_stderr; }; } // namespace Internal diff --git a/src/plugins/mesonprojectmanager/mesonprojectplugin.cpp b/src/plugins/mesonprojectmanager/mesonprojectplugin.cpp index cc1e3eb1e27..71ef57462b2 100644 --- a/src/plugins/mesonprojectmanager/mesonprojectplugin.cpp +++ b/src/plugins/mesonprojectmanager/mesonprojectplugin.cpp @@ -12,36 +12,20 @@ #include "toolssettingsaccessor.h" #include "toolssettingspage.h" -#include - #include #include #include #include -using namespace Core; using namespace ProjectExplorer; using namespace Utils; namespace MesonProjectManager::Internal { -class MesonProjectPluginPrivate : public QObject +class MesonProjectPluginPrivate { - Q_OBJECT public: - MesonProjectPluginPrivate() - { - MesonTools::setTools(m_toolsSettings.loadMesonTools(ICore::dialogParent())); - connect(ICore::instance(), - &ICore::saveSettingsRequested, - this, - &MesonProjectPluginPrivate::saveAll); - } - - ~MesonProjectPluginPrivate() {} - -private: ToolsSettingsPage m_toolslSettingsPage; ToolsSettingsAccessor m_toolsSettings; MesonBuildStepFactory m_buildStepFactory; @@ -50,11 +34,6 @@ private: MesonActionsManager m_actions; MachineFileManager m_machineFilesManager; SimpleTargetRunnerFactory m_mesonRunWorkerFactory{{m_runConfigurationFactory.runConfigurationId()}}; - - void saveAll() - { - m_toolsSettings.saveMesonTools(MesonTools::tools(), ICore::dialogParent()); - } }; MesonProjectPlugin::~MesonProjectPlugin() @@ -72,5 +51,3 @@ void MesonProjectPlugin::initialize() } } // MesonProjectManager::Internal - -#include "mesonprojectplugin.moc" diff --git a/src/plugins/mesonprojectmanager/toolssettingsaccessor.cpp b/src/plugins/mesonprojectmanager/toolssettingsaccessor.cpp index 3f5cdfa45a7..80e5274c481 100644 --- a/src/plugins/mesonprojectmanager/toolssettingsaccessor.cpp +++ b/src/plugins/mesonprojectmanager/toolssettingsaccessor.cpp @@ -6,6 +6,7 @@ #include "mesonpluginconstants.h" #include "mesonprojectmanagertr.h" +#include #include #include @@ -16,6 +17,7 @@ #include #include +using namespace Core; using namespace Utils; namespace MesonProjectManager { @@ -30,11 +32,16 @@ ToolsSettingsAccessor::ToolsSettingsAccessor() { setDocType("QtCreatorMesonTools"); setApplicationDisplayName(QGuiApplication::applicationDisplayName()); - setBaseFilePath(Core::ICore::userResourcePath(Constants::ToolsSettings::FILENAME)); + setBaseFilePath(ICore::userResourcePath(Constants::ToolsSettings::FILENAME)); + + MesonTools::setTools(loadMesonTools()); + + QObject::connect(ICore::instance(), &ICore::saveSettingsRequested, [this] { + saveMesonTools(MesonTools::tools()); + }); } -void ToolsSettingsAccessor::saveMesonTools(const std::vector &tools, - QWidget *parent) +void ToolsSettingsAccessor::saveMesonTools(const std::vector &tools) { using namespace Constants; Store data; @@ -51,13 +58,13 @@ void ToolsSettingsAccessor::saveMesonTools(const std::vector entry_count++; } data.insert(ToolsSettings::ENTRY_COUNT, entry_count); - saveSettings(data, parent); + saveSettings(data, ICore::dialogParent()); } -std::vector ToolsSettingsAccessor::loadMesonTools(QWidget *parent) +std::vector ToolsSettingsAccessor::loadMesonTools() { using namespace Constants; - auto data = restoreSettings(parent); + auto data = restoreSettings(ICore::dialogParent()); auto entry_count = data.value(ToolsSettings::ENTRY_COUNT, 0).toInt(); std::vector result; for (auto toolIndex = 0; toolIndex < entry_count; toolIndex++) { diff --git a/src/plugins/mesonprojectmanager/toolssettingsaccessor.h b/src/plugins/mesonprojectmanager/toolssettingsaccessor.h index 61cc505d3a7..10fce88099d 100644 --- a/src/plugins/mesonprojectmanager/toolssettingsaccessor.h +++ b/src/plugins/mesonprojectmanager/toolssettingsaccessor.h @@ -14,8 +14,9 @@ class ToolsSettingsAccessor final : public Utils::UpgradingSettingsAccessor { public: ToolsSettingsAccessor(); - void saveMesonTools(const std::vector &tools, QWidget *parent); - std::vector loadMesonTools(QWidget *parent); + + void saveMesonTools(const std::vector &tools); + std::vector loadMesonTools(); }; } // namespace Internal diff --git a/src/plugins/projectexplorer/devicesupport/sshparameters.cpp b/src/plugins/projectexplorer/devicesupport/sshparameters.cpp index 744e350eaef..22055cf7946 100644 --- a/src/plugins/projectexplorer/devicesupport/sshparameters.cpp +++ b/src/plugins/projectexplorer/devicesupport/sshparameters.cpp @@ -77,6 +77,7 @@ bool SshParameters::setupSshEnvironment(Process *process) const bool hasDisplay = env.hasKey("DISPLAY") && (env.value("DISPLAY") != QString(":0")); if (SshSettings::askpassFilePath().exists()) { env.set("SSH_ASKPASS", SshSettings::askpassFilePath().toUserOutput()); + env.set("SSH_ASKPASS_REQUIRE", "force"); // OpenSSH only uses the askpass program if DISPLAY is set, regardless of the platform. if (!env.hasKey("DISPLAY")) diff --git a/src/plugins/python/pyside.cpp b/src/plugins/python/pyside.cpp index 3451b2b9782..bae0c0753af 100644 --- a/src/plugins/python/pyside.cpp +++ b/src/plugins/python/pyside.cpp @@ -86,20 +86,36 @@ void PySideInstaller::installPyside(const FilePath &python, { QMap availablePySides; - const QString hostQtTail = HostOsInfo::isMacHost() ? QString("Tools/sdktool") - : QString("Tools/sdktool/share/qtcreator"); + const Utils::QtcSettings *settings = Core::ICore::settings(QSettings::SystemScope); - const std::optional qtInstallDir - = QtSupport::LinkWithQtSupport::linkedQt().tailRemoved(hostQtTail); - if (qtInstallDir) { - const FilePath qtForPythonDir = qtInstallDir->pathAppended("QtForPython"); - for (const FilePath &versionDir : qtForPythonDir.dirEntries(QDir::Dirs | QDir::NoDotAndDotDot)) { - FilePath requirements = versionDir.pathAppended("requirements.txt"); - if (requirements.exists()) - availablePySides[QVersionNumber::fromString(versionDir.fileName())] = requirements; + const FilePaths requirementsList + = Utils::transform(settings->value("Python/PySideWheelsRequirements").toList(), + &FilePath::fromSettings); + for (const FilePath &requirements : requirementsList) { + if (requirements.exists()) { + auto version = QVersionNumber::fromString(requirements.parentDir().fileName()); + availablePySides[version] = requirements; } } + if (requirementsList.isEmpty()) { // fallback remove in Qt Creator 13 + const QString hostQtTail = HostOsInfo::isMacHost() + ? QString("Tools/sdktool") + : QString("Tools/sdktool/share/qtcreator"); + + const std::optional qtInstallDir + = QtSupport::LinkWithQtSupport::linkedQt().tailRemoved(hostQtTail); + if (qtInstallDir) { + const FilePath qtForPythonDir = qtInstallDir->pathAppended("QtForPython"); + for (const FilePath &versionDir : + qtForPythonDir.dirEntries(QDir::Dirs | QDir::NoDotAndDotDot)) { + FilePath requirements = versionDir.pathAppended("requirements.txt"); + if (!requirementsList.contains(requirements) && requirements.exists()) + availablePySides[QVersionNumber::fromString(versionDir.fileName())] + = requirements; + } + } + } auto install = new PipInstallTask(python); connect(install, &PipInstallTask::finished, install, &QObject::deleteLater); diff --git a/src/plugins/python/pythonsettings.cpp b/src/plugins/python/pythonsettings.cpp index 6cc3ab3e182..57d951ec1d2 100644 --- a/src/plugins/python/pythonsettings.cpp +++ b/src/plugins/python/pythonsettings.cpp @@ -21,13 +21,14 @@ #include #include -#include +#include #include #include -#include #include +#include #include #include +#include #include #include @@ -366,14 +367,6 @@ private: InterpreterOptionsWidget *m_widget = nullptr; }; -static bool alreadyRegistered(const QList &pythons, const FilePath &pythonExecutable) -{ - return Utils::anyOf(pythons, [pythonExecutable](const Interpreter &interpreter) { - return interpreter.command.toFileInfo().canonicalFilePath() - == pythonExecutable.toFileInfo().canonicalFilePath(); - }); -} - static InterpreterOptionsPage &interpreterOptionsPage() { static InterpreterOptionsPage page; @@ -626,8 +619,9 @@ static void disableOutdatedPyls() } } -static void addPythonsFromRegistry(QList &pythons) +static QList pythonsFromRegistry() { + QList pythons; QSettings pythonRegistry("HKEY_LOCAL_MACHINE\\SOFTWARE\\Python\\PythonCore", QSettings::NativeFormat); for (const QString &versionGroup : pythonRegistry.childGroups()) { @@ -636,7 +630,7 @@ static void addPythonsFromRegistry(QList &pythons) QVariant regVal = pythonRegistry.value("InstallPath/ExecutablePath"); if (regVal.isValid()) { const FilePath &executable = FilePath::fromUserInput(regVal.toString()); - if (executable.exists() && !alreadyRegistered(pythons, executable)) { + if (executable.exists()) { pythons << Interpreter{QUuid::createUuid().toString(), name, FilePath::fromUserInput(regVal.toString())}; @@ -645,7 +639,7 @@ static void addPythonsFromRegistry(QList &pythons) regVal = pythonRegistry.value("InstallPath/WindowedExecutablePath"); if (regVal.isValid()) { const FilePath &executable = FilePath::fromUserInput(regVal.toString()); - if (executable.exists() && !alreadyRegistered(pythons, executable)) { + if (executable.exists()) { pythons << Interpreter{QUuid::createUuid().toString(), //: (Windowed) Tr::tr("%1 (Windowed)").arg(name), @@ -656,28 +650,30 @@ static void addPythonsFromRegistry(QList &pythons) if (regVal.isValid()) { const FilePath &path = FilePath::fromUserInput(regVal.toString()); const FilePath python = path.pathAppended("python").withExecutableSuffix(); - if (python.exists() && !alreadyRegistered(pythons, python)) + if (python.exists()) pythons << createInterpreter(python, "Python " + versionGroup); const FilePath pythonw = path.pathAppended("pythonw").withExecutableSuffix(); - if (pythonw.exists() && !alreadyRegistered(pythons, pythonw)) + if (pythonw.exists()) pythons << createInterpreter(pythonw, "Python " + versionGroup, "(Windowed)"); } pythonRegistry.endGroup(); } + return pythons; } -static void addPythonsFromPath(QList &pythons) +static QList pythonsFromPath() { + QList pythons; if (HostOsInfo::isWindowsHost()) { for (const FilePath &executable : FilePath("python").searchAllInPath()) { // Windows creates empty redirector files that may interfere if (executable.toFileInfo().size() == 0) continue; - if (executable.exists() && !alreadyRegistered(pythons, executable)) + if (executable.exists()) pythons << createInterpreter(executable, "Python from Path"); } for (const FilePath &executable : FilePath("pythonw").searchAllInPath()) { - if (executable.exists() && !alreadyRegistered(pythons, executable)) + if (executable.exists()) pythons << createInterpreter(executable, "Python from Path", "(Windowed)"); } } else { @@ -690,11 +686,12 @@ static void addPythonsFromPath(QList &pythons) const QDir dir(path.toString()); for (const QFileInfo &fi : dir.entryInfoList(filters)) { const FilePath executable = Utils::FilePath::fromFileInfo(fi); - if (executable.exists() && !alreadyRegistered(pythons, executable)) + if (executable.exists()) pythons << createInterpreter(executable, "Python from Path"); } } } + return pythons; } static QString idForPythonFromPath(const QList &pythons) @@ -713,6 +710,51 @@ static QString idForPythonFromPath(const QList &pythons) static PythonSettings *settingsInstance = nullptr; +static bool alreadyRegistered(const Interpreter &candidate) +{ + return Utils::anyOf(settingsInstance->interpreters(), + [candidate = candidate.command](const Interpreter &interpreter) { + return interpreter.command.isSameDevice(candidate) + && interpreter.command.resolveSymlinks() + == candidate.resolveSymlinks(); + }); +} + +static void scanPath() +{ + auto watcher = new QFutureWatcher>(); + QObject::connect(watcher, &QFutureWatcher>::finished, [watcher]() { + for (const Interpreter &interpreter : watcher->result()) { + if (!alreadyRegistered(interpreter)) + settingsInstance->addInterpreter(interpreter); + } + watcher->deleteLater(); + }); + watcher->setFuture(Utils::asyncRun(pythonsFromPath)); +} + +static void scanRegistry() +{ + auto watcher = new QFutureWatcher>(); + QObject::connect(watcher, &QFutureWatcher>::finished, [watcher]() { + for (const Interpreter &interpreter : watcher->result()) { + if (!alreadyRegistered(interpreter)) + settingsInstance->addInterpreter(interpreter); + } + watcher->deleteLater(); + scanPath(); + }); + watcher->setFuture(Utils::asyncRun(pythonsFromRegistry)); +} + +static void scanSystemForInterpreters() +{ + if (Utils::HostOsInfo::isWindowsHost()) + scanRegistry(); + else + scanPath(); +} + PythonSettings::PythonSettings() { QTC_ASSERT(!settingsInstance, return); @@ -723,9 +765,7 @@ PythonSettings::PythonSettings() initFromSettings(Core::ICore::settings()); - if (HostOsInfo::isWindowsHost()) - addPythonsFromRegistry(m_interpreters); - addPythonsFromPath(m_interpreters); + scanSystemForInterpreters(); if (m_defaultInterpreterId.isEmpty()) m_defaultInterpreterId = idForPythonFromPath(m_interpreters); diff --git a/src/plugins/vcsbase/vcsbaseplugin.cpp b/src/plugins/vcsbase/vcsbaseplugin.cpp index 6880e42e5a2..29a25e1627b 100644 --- a/src/plugins/vcsbase/vcsbaseplugin.cpp +++ b/src/plugins/vcsbase/vcsbaseplugin.cpp @@ -751,8 +751,10 @@ FilePath source(IDocument *document) void setProcessEnvironment(Environment *e) { const QString prompt = Internal::commonSettings().sshPasswordPrompt().path(); - if (!prompt.isEmpty()) + if (!prompt.isEmpty()) { e->set("SSH_ASKPASS", prompt); + e->set("SSH_ASKPASS_REQUIRE", "force"); + } } } // namespace VcsBase diff --git a/tests/manual/meson/mesonsampleproject/meson.build b/tests/manual/meson/mesonsampleproject/meson.build index 901285a587e..8edea0d4d53 100644 --- a/tests/manual/meson/mesonsampleproject/meson.build +++ b/tests/manual/meson/mesonsampleproject/meson.build @@ -4,7 +4,7 @@ project('mesonsampleproject', 'cpp',default_options : ['cpp_std=c++11']) qt5 = import('qt5') qt5dep = dependency('qt5', modules : ['Core', 'Widgets']) -translations = qt5.compile_translations(ts_files : 'mesonsampleproject_fr_FR.ts', build_by_default : true) +#translations = qt5.compile_translations(ts_files : 'mesonsampleproject_fr_FR.ts', build_by_default : true) generated_files = qt5.preprocess( moc_headers : 'mesonsampleproject.h', diff --git a/tests/system/objects.map b/tests/system/objects.map index cc359d6b7fc..940739813a9 100644 --- a/tests/system/objects.map +++ b/tests/system/objects.map @@ -126,7 +126,7 @@ :Qt Creator.DragDoc_QToolButton {toolTip='Drag to drag documents between splits' type='QToolButton' unnamed='1' visible='1' window=':Qt Creator_Core::Internal::MainWindow'} :Qt Creator.Events_QDockWidget {name='QmlProfiler.Statistics.DockDockWidget' type='QDockWidget' visible='1' window=':Qt Creator_Core::Internal::MainWindow'} :Qt Creator.Events_QTabBar {aboveWidget=':Qt Creator.Events_QDockWidget' type='QTabBar' unnamed='1' visible='1' window=':Qt Creator_Core::Internal::MainWindow'} -:Qt Creator.Issues_QListView {type='Utils::TreeView' unnamed='1' visible='1' window=':Qt Creator_Core::Internal::MainWindow' windowTitle='Issues'} +:Qt Creator.Issues_QListView {type='QTreeView' unnamed='1' visible='1' window=':Qt Creator_Core::Internal::MainWindow' windowTitle='Issues'} :Qt Creator.Project.Menu.File_QMenu {name='Project.Menu.File' type='QMenu'} :Qt Creator.Project.Menu.Folder_QMenu {name='Project.Menu.Folder' type='QMenu' visible='1'} :Qt Creator.QML debugging and profiling:_QComboBox {leftWidget=':Qt Creator.QML debugging and profiling:_QLabel' type='QComboBox' unnamed='1' visible='1' window=':Qt Creator_Core::Internal::MainWindow'} diff --git a/tests/system/shared/project.py b/tests/system/shared/project.py index b39ef8060ee..317067142a4 100644 --- a/tests/system/shared/project.py +++ b/tests/system/shared/project.py @@ -81,8 +81,8 @@ def __createProjectOrFileSelectType__(category, template, fromWelcome = False, i return __getSupportedPlatforms__(str(text), template)[0] def __createProjectSetNameAndPath__(path, projectName = None, checks = True): - directoryEdit = waitForObject("{type='Utils::FancyLineEdit' unnamed='1' visible='1' " - "toolTip~='Full path: .*'}") + pathChooser = waitForObject("{type='Utils::PathChooser' name='baseFolder' visible='1'}") + directoryEdit = getChildByClass(pathChooser, "Utils::FancyLineEdit") replaceEditorContent(directoryEdit, path) projectNameEdit = waitForObject("{name='nameLineEdit' visible='1' " "type='Utils::FancyLineEdit'}")