diff --git a/appimage/kdevelop-recipe-centos6.sh b/appimage/kdevelop-recipe-centos6.sh index 3fea738f3f..1c58dac6b7 100755 --- a/appimage/kdevelop-recipe-centos6.sh +++ b/appimage/kdevelop-recipe-centos6.sh @@ -1,513 +1,512 @@ #!/bin/bash # Halt on errors set -e # Be verbose set -x # Now we are inside CentOS 6 grep -r "CentOS release 6" /etc/redhat-release || exit 1 git_pull_rebase_helper() { git fetch git stash || true git rebase $(git rev-parse --abbrev-ref --symbolic-full-name @{u}) || true git stash pop || true } SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" QTVERSION=5.9.2 QTVERSION_SHORT=5.9 QTDIR=/usr/local/Qt-${QTVERSION}/ if [ -z "$KDEVELOP_VERSION" ]; then - KDEVELOP_VERSION=5.1 + KDEVELOP_VERSION=5.3 fi if [ -z "$KDEV_PG_QT_VERSION" ]; then KDEV_PG_QT_VERSION=2.0 fi -KF5_VERSION=v5.37.0 -KDE_PLASMA_VERSION=v5.12.3 # note: need libksysguard commit a0e69617442d720c76da5ebe3323e7a977929db4 (patch which makes plasma dep optional) -KDE_APPLICATION_VERSION=v16.12.3 +KF5_VERSION=v5.48.0 # note: v5.49.0 is broken due to https://phabricator.kde.org/R246:0a96acf251baa5c9dd042d093ab2bf8fcee10502 +KDE_PLASMA_VERSION=v5.13.4 # note: need libksysguard commit a0e69617442d720c76da5ebe3323e7a977929db4 (patch which makes plasma dep optional) +KDE_APPLICATION_VERSION=v18.08.0 GRANTLEE_VERSION=v5.1.0 export LLVM_ROOT=/opt/llvm/ export PATH=/opt/rh/python27/root/usr/bin/:$PATH export LD_LIBRARY_PATH=/opt/rh/python27/root/usr/lib64:$LD_LIBRARY_PATH # qjsonparser, used to add metadata to the plugins needs to work in a en_US.UTF-8 environment. That's # not always set correctly in CentOS 6.7 export LC_ALL=en_US.UTF-8 export LANG=en_us.UTF-8 # Determine which architecture should be built if [[ "$(arch)" = "i686" || "$(arch)" = "x86_64" ]] ; then ARCH=$(arch) else echo "Architecture could not be determined" exit 1 fi # Make sure we build from the /, parts of this script depends on that. We also need to run as root... cd / # Build AppImageKit #rm -Rf /AppImageKit if [ ! -d AppImageKit ] ; then git clone --depth 1 https://github.com/probonopd/AppImageKit.git /AppImageKit fi cd /AppImageKit/ git checkout stable/v1.0 git_pull_rebase_helper git reset --hard ./build.sh cd / # Use the new compiler . /opt/rh/devtoolset-4/enable # TODO: Use these vars more export FAKEROOT=/kdevelop.appdir export PREFIX=/kdevelop.appdir/usr/ export SRC=$HOME/src/ export BUILD=$HOME/build export CMAKE_PREFIX_PATH=$QTDIR:/kdevelop.appdir/share/llvm/ # if the library path doesn't point to our usr/lib, linking will be broken and we won't find all deps either export LD_LIBRARY_PATH=/usr/lib64/:/usr/lib:/kdevelop.appdir/usr/lib:$QTDIR/lib/:/opt/python3.6/lib/:$LD_LIBRARY_PATH # Workaround for: On CentOS 6, .pc files in /usr/lib/pkgconfig are not recognized # However, this is where .pc files get installed when bulding libraries... (FIXME) # I found this by comparing the output of librevenge's "make install" command # between Ubuntu and CentOS 6 ln -sf /usr/share/pkgconfig /usr/lib/pkgconfig # Prepare the install location if [ -z "$SKIP_PRUNE" ]; then rm -rf /kdevelop.appdir/ || true mkdir -p /kdevelop.appdir/usr # refresh ldconfig cache ldconfig # make sure lib and lib64 are the same thing mkdir -p /kdevelop.appdir/usr/lib cd /kdevelop.appdir/usr ln -s lib lib64 fi # start building the deps function build_project { ( PROJECT=$1 VERSION=$2 # clone if not there mkdir -p $SRC cd $SRC if ( test -d $PROJECT ) then echo "$PROJECT already cloned" cd $PROJECT git stash git reset --hard git fetch git fetch --tags cd .. else if [ -z "$CUSTOM_GIT_URL" ]; then git clone git://anongit.kde.org/$PROJECT else git clone $CUSTOM_GIT_URL fi fi cd $PROJECT git checkout $VERSION git rebase $(git rev-parse --abbrev-ref --symbolic-full-name @{u}) || true # git rebase will fail if a tag is checked out git stash pop || true cd .. if [ "$PROJECT" = "knotifications" ]; then cd $PROJECT echo "patching knotifications" git reset --hard cat > no_phonon.patch << EOF diff --git a/CMakeLists.txt b/CMakeLists.txt index b97425f..8f15f08 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -59,10 +59,10 @@ find_package(KF5Config ${KF5_DEP_VERSION} REQUIRED) find_package(KF5Codecs ${KF5_DEP_VERSION} REQUIRED) find_package(KF5CoreAddons ${KF5_DEP_VERSION} REQUIRED) -find_package(Phonon4Qt5 4.6.60 REQUIRED NO_MODULE) +find_package(Phonon4Qt5 4.6.60 NO_MODULE) set_package_properties(Phonon4Qt5 PROPERTIES DESCRIPTION "Qt-based audio library" - TYPE REQUIRED + TYPE OPTIONAL PURPOSE "Required to build audio notification support") if (Phonon4Qt5_FOUND) add_definitions(-DHAVE_PHONON4QT5) EOF cat no_phonon.patch |patch -p1 cd .. fi # create build dir mkdir -p $BUILD/$PROJECT # go there cd $BUILD/$PROJECT # cmake it cmake3 $SRC/$PROJECT -G Ninja -DBUILD_TESTING=OFF -DCMAKE_BUILD_TYPE=RelWithDebInfo -DCMAKE_INSTALL_PREFIX:PATH=$PREFIX $3 # make ninja # install ninja install ) } function build_framework { ( build_project $1 $KF5_VERSION $2 ) } # KDE Frameworks if [ -z "$SKIP_FRAMEWORKS" ]; then build_framework extra-cmake-modules build_framework kconfig build_framework kguiaddons build_framework ki18n build_framework kitemviews build_framework sonnet build_framework kwindowsystem build_framework kwidgetsaddons build_framework kcompletion build_framework kdbusaddons build_framework karchive build_framework kcoreaddons build_framework kjobwidgets build_framework kcrash build_framework kservice build_framework kcodecs build_framework kauth build_framework kconfigwidgets build_framework kiconthemes build_framework ktextwidgets build_framework kglobalaccel build_framework kxmlgui build_framework kbookmarks build_framework solid build_framework kio build_framework kparts build_framework kitemmodels build_framework threadweaver build_framework attica build_framework knewstuff build_framework syntax-highlighting build_framework ktexteditor build_framework kpackage build_framework kdeclarative build_framework kcmutils build_framework knotifications build_framework knotifyconfig build_framework kdoctools build_framework breeze-icons -DBINARY_ICONS_RESOURCE=1 build_framework kpty build_framework kinit fi # KDE Plasma build_project libksysguard $KDE_PLASMA_VERSION build_project kdecoration $KDE_PLASMA_VERSION # for breeze build_project breeze $KDE_PLASMA_VERSION # KDE Applications build_project libkomparediff2 $KDE_APPLICATION_VERSION build_project kate $KDE_APPLICATION_VERSION # for snippet plugin, see T3826 build_project konsole $KDE_APPLICATION_VERSION # Extra (CUSTOM_GIT_URL=https://github.com/steveire/grantlee.git build_project grantlee $GRANTLEE_VERSION) # KDevelop build_project kdevelop-pg-qt $KDEV_PG_QT_VERSION -build_project kdevplatform $KDEVELOP_VERSION build_project kdevelop $KDEVELOP_VERSION build_project kdev-php $KDEVELOP_VERSION # Build kdev-python export LD_LIBRARY_PATH=$LD_LIBRARY_PATH/kdevelop.appdir/usr/lib/ build_project kdev-python $KDEVELOP_VERSION # Install some colorschemes cd $SRC $SCRIPT_DIR/install_colorschemes.sh cd /kdevelop.appdir # FIXME: How to find out which subset of plugins is really needed? I used strace when running the binary mkdir -p ./usr/lib/qt5/plugins/ if [ -e $(dirname $QTDIR/plugins/bearer) ] ; then PLUGINS=$(dirname $QTDIR/plugins/bearer) else PLUGINS=../../$QTVERSION_SHORT/gc*/plugins/ fi echo $PLUGINS # /usr/lib64/qt5/plugins if build system Qt is found cp -r $PLUGINS/bearer ./usr/lib/qt5/plugins/ cp -r $PLUGINS/generic ./usr/lib/qt5/plugins/ cp -r $PLUGINS/imageformats ./usr/lib/qt5/plugins/ cp -r $PLUGINS/platforms ./usr/lib/qt5/plugins/ cp -r $PLUGINS/iconengines ./usr/lib/qt5/plugins/ cp -r $PLUGINS/platforminputcontexts ./usr/lib/qt5/plugins/ # cp -r $PLUGINS/platformthemes ./usr/lib/qt5/plugins/ cp -r $PLUGINS/xcbglintegrations ./usr/lib/qt5/plugins/ cp -R /kdevelop.appdir/usr/lib/grantlee/ /kdevelop.appdir/usr/lib/qt5/plugins/ rm -Rf /kdevelop.appdir/usr/lib/grantlee cp -ru /usr/share/mime/* /kdevelop.appdir/usr/share/mime update-mime-database /kdevelop.appdir/usr/share/mime/ cp -R ./usr/lib/plugins/* ./usr/lib/qt5/plugins/ rm -Rf ./usr/lib/plugins/ cp $(ldconfig -p | grep libsasl2.so.2 | cut -d ">" -f 2 | xargs) ./usr/lib/ # Fedora 23 seemed to be missing SOMETHING from the Centos 6.7. The only message was: # This application failed to start because it could not find or load the Qt platform plugin "xcb". # Setting export QT_DEBUG_PLUGINS=1 revealed the cause. # QLibraryPrivate::loadPlugin failed on "/usr/lib64/qt5/plugins/platforms/libqxcb.so" : # "Cannot load library /usr/lib64/qt5/plugins/platforms/libqxcb.so: (/lib64/libEGL.so.1: undefined symbol: drmGetNodeTypeFromFd)" # Which means that we have to copy libEGL.so.1 in too cp $(ldconfig -p | grep libEGL.so.1 | cut -d ">" -f 2 | xargs) ./usr/lib/ # Otherwise F23 cannot load the Qt platform plugin "xcb" cp $(ldconfig -p | grep libxcb.so.1 | cut -d ">" -f 2 | xargs) ./usr/lib/ cp $(ldconfig -p | grep libfreetype.so.6 | cut -d ">" -f 2 | xargs) ./usr/lib/ # For Fedora 20 ldd usr/bin/kdevelop | grep "=>" | awk '{print $3}' | xargs -I '{}' cp -v '{}' ./usr/lib || true cp /usr/bin/cmake usr/bin/cmake ldd usr/bin/cmake | grep "=>" | awk '{print $3}' | xargs -I '{}' cp -v '{}' ./usr/lib || true #ldd usr/lib64/kdevelop/*.so | grep "=>" | awk '{print $3}' | xargs -I '{}' cp -v '{}' ./usr/lib || true #ldd usr/lib64/plugins/imageformats/*.so | grep "=>" | awk '{print $3}' | xargs -I '{}' cp -v '{}' ./usr/lib || true ldd usr/lib/qt5/plugins/platforms/libqxcb.so | grep "=>" | awk '{print $3}' | xargs -I '{}' cp -v '{}' ./usr/lib || true # Copy in the indirect dependencies FILES=$(find . -type f -executable) for FILE in $FILES ; do echo "*** Processing:" $FILE ldd "${FILE}" | grep "=>" | awk '{print $3}' | xargs -I '{}' cp -vu '{}' usr/lib || true done # The following are assumed to be part of the base system rm -f usr/lib/libcom_err.so.2 || true rm -f usr/lib/libcrypt.so.1 || true rm -f usr/lib/libdl.so.2 || true rm -f usr/lib/libexpat.so.1 || true #rm -f usr/lib/libfontconfig.so.1 || true rm -f usr/lib/libgcc_s.so.1 || true rm -f usr/lib/libglib-2.0.so.0 || true rm -f usr/lib/libgpg-error.so.0 || true rm -f usr/lib/libgssapi_krb5.so.2 || true rm -f usr/lib/libgssapi.so.3 || true rm -f usr/lib/libhcrypto.so.4 || true rm -f usr/lib/libheimbase.so.1 || true rm -f usr/lib/libheimntlm.so.0 || true rm -f usr/lib/libhx509.so.5 || true rm -f usr/lib/libICE.so.6 || true rm -f usr/lib/libidn.so.11 || true rm -f usr/lib/libk5crypto.so.3 || true rm -f usr/lib/libkeyutils.so.1 || true rm -f usr/lib/libkrb5.so.26 || true rm -f usr/lib/libkrb5.so.3 || true rm -f usr/lib/libkrb5support.so.0 || true # rm -f usr/lib/liblber-2.4.so.2 || true # needed for debian wheezy # rm -f usr/lib/libldap_r-2.4.so.2 || true # needed for debian wheezy rm -f usr/lib/libm.so.6 || true rm -f usr/lib/libp11-kit.so.0 || true rm -f usr/lib/libpcre.so.3 || true rm -f usr/lib/libpthread.so.0 || true rm -f usr/lib/libresolv.so.2 || true rm -f usr/lib/libroken.so.18 || true rm -f usr/lib/librt.so.1 || true rm -f usr/lib/libSM.so.6 || true rm -f usr/lib/libusb-1.0.so.0 || true rm -f usr/lib/libuuid.so.1 || true rm -f usr/lib/libwind.so.0 || true # Remove these libraries, we need to use the system versions; this means 11.04 is not supported (12.04 is our baseline) rm -f usr/lib/libGL.so.* || true rm -f usr/lib/libdrm.so.* || true #rm -f usr/lib/libz.so.1 || true # These seem to be available on most systems but not Ubuntu 11.04 # rm -f usr/lib/libffi.so.6 usr/lib/libGL.so.1 usr/lib/libglapi.so.0 usr/lib/libxcb.so.1 usr/lib/libxcb-glx.so.0 || true # Delete potentially dangerous libraries rm -f usr/lib/libstdc* usr/lib/libgobject* usr/lib/libc.so.* || true # Do NOT delete libX* because otherwise on Ubuntu 11.04: # loaded library "Xcursor" malloc.c:3096: sYSMALLOc: Assertion (...) Aborted # We don't bundle the developer stuff rm -rf usr/include || true rm -rf usr/lib/cmake || true rm -rf usr/lib/pkgconfig || true rm -rf usr/share/ECM/ || true rm -rf usr/share/gettext || true rm -rf usr/share/pkgconfig || true strip -g $(find usr) || true # We do not bundle this, so let's not search that inside the AppImage. # Fixes "Qt: Failed to create XKB context!" and lets us enter text #sed -i -e 's|././/share/X11/|/usr/share/X11/|g' ./usr/lib/qt5/plugins/platforminputcontexts/libcomposeplatforminputcontextplugin.so #sed -i -e 's|././/share/X11/|/usr/share/X11/|g' ./usr/lib/libQt5XcbQpa.so.5 # Workaround for: # D-Bus library appears to be incorrectly set up; # failed to read machine uuid: Failed to open # The file is more commonly in /etc/machine-id # sed -i -e 's|/var/lib/dbus/machine-id|//././././etc/machine-id|g' ./usr/lib/libdbus-1.so.3 # or rm -f ./usr/lib/libdbus-1.so.3 || true # Remove python rm -f ./usr/bin/python* rm -f ./usr/bin/pydoc* rm -f ./usr/bin/pyenv* # remove big execs rm -f ./usr/bin/verify-uselistorder rm -f ./usr/bin/obj2yaml ./usr/bin/yaml2obj rm -f ./usr/bin/kwrite ./usr/bin/kate cp /kdevelop.appdir/usr/lib/libexec/kf5/* /kdevelop.appdir/usr/bin/ cd / if [ ! -d appimage-exec-wrapper ]; then git clone git://anongit.kde.org/scratch/brauch/appimage-exec-wrapper fi; cd /appimage-exec-wrapper/ make clean make cd /kdevelop.appdir cp -v /appimage-exec-wrapper/exec.so exec_wrapper.so cat > AppRun << EOF #!/bin/bash DIR="\`dirname \"\$0\"\`" DIR="\`( cd \"\$DIR\" && pwd )\`" export APPDIR=\$DIR export LD_PRELOAD=\$DIR/exec_wrapper.so export APPIMAGE_ORIGINAL_QML2_IMPORT_PATH=\$QML2_IMPORT_PATH export APPIMAGE_ORIGINAL_LD_LIBRARY_PATH=\$LD_LIBRARY_PATH export APPIMAGE_ORIGINAL_QT_PLUGIN_PATH=\$QT_PLUGIN_PATH export APPIMAGE_ORIGINAL_XDG_DATA_DIRS=\$XDG_DATA_DIRS export APPIMAGE_ORIGINAL_PATH=\$PATH export APPIMAGE_ORIGINAL_PYTHONHOME=\$PYTHONHOME export QML2_IMPORT_PATH=\$DIR/usr/lib/qml:\$QML2_IMPORT_PATH export LD_LIBRARY_PATH=\$DIR/usr/lib/:\$LD_LIBRARY_PATH export QT_PLUGIN_PATH=\$DIR/usr/lib/qt5/plugins/ export XDG_DATA_DIRS=\$DIR/usr/share/:\$XDG_DATA_DIRS export PATH=\$DIR/usr/bin:\$PATH export KDE_FORK_SLAVES=1 export PYTHONHOME=\$DIR/usr/ export APPIMAGE_STARTUP_QML2_IMPORT_PATH=\$QML2_IMPORT_PATH export APPIMAGE_STARTUP_LD_LIBRARY_PATH=\$LD_LIBRARY_PATH export APPIMAGE_STARTUP_QT_PLUGIN_PATH=\$QT_PLUGIN_PATH export APPIMAGE_STARTUP_XDG_DATA_DIRS=\$XDG_DATA_DIRS export APPIMAGE_STARTUP_PATH=\$PATH export APPIMAGE_STARTUP_PYTHONHOME=\$PYTHONHOME export KDEV_DISABLE_PLUGINS=KDevWelcomePage cd \$HOME kdevelop \$@ EOF chmod +x AppRun cat > kdevelop.desktop << EOF [Desktop Entry] GenericName=Integrated development environment Name=KDevelop MimeType=text/plain; Exec=AppRun -b %U StartupNotify=true X-KDE-HasTempFileOption=true Icon=kdevelop X-DocPath=kdevelop/index.html Type=Application Terminal=false InitialPreference=9 Categories=Qt;KDE;Utility;TextEditor; EOF cp $SRC/kdevelop/app/icons/48-apps-kdevelop.png kdevelop.png cp -R /usr/lib/python3.6 /kdevelop.appdir/usr/lib/ rm -Rf /kdevelop.appdir/usr/lib/python3.6/{test,config-3.5m,__pycache__,site-packages,lib-dynload,distutils,idlelib,unittest,tkinter,ensurepip} mkdir -p /kdevelop.appdir/usr/share/kdevelop/ cp $BUILD/breeze-icons/icons/breeze-icons.rcc /kdevelop.appdir/usr/share/kdevelop/icontheme.rcc rm -Rf /kdevelop.appdir/usr/share/icons/breeze* # not needed because of the rcc rm -f /kdevelop.appdir/usr/bin/llvm* rm -f /kdevelop.appdir/usr/bin/clang* rm -f /kdevelop.appdir/usr/bin/opt rm -f /kdevelop.appdir/usr/bin/lli rm -f /kdevelop.appdir/usr/bin/sancov rm -f /kdevelop.appdir/usr/bin/cmake rm -f /kdevelop.appdir/usr/bin/python rm -Rf /kdevelop.appdir/usr/lib/pkgconfig rm -Rf /kdevelop.appdir/usr/share/man rm -Rf /kdevelop.appdir/usr/share/locale rm -Rf /kdevelop.appdir/usr/lib/libLTO.so #At first it seems like "we shouldn't ship X11", but actually we should; the X11 protocol is sort of guaranteed to stay compatible, #while these libraries are not. # rm -Rf /kdevelop.appdir/usr/lib/libxcb* # add that back in # cp /usr/lib64/libxcb-keysyms.so.1 /kdevelop.appdir/usr/lib/ # rm -Rf /kdevelop.appdir/usr/lib/{libX11.so.6,libXau.so.6,libXext.so.6,libXi.so.6,libXxf86vm.so.1,libX11-xcb.so.1,libXdamage.so.1,libXfixes.so.3,libXrender.so.1} rm -f /kdevelop.appdir/usr/bin/llc rm -f /kdevelop.appdir/usr/bin/bugpoint find /kdevelop.appdir -name '*.a' -exec rm {} \; cd / APP=KDevelop VERSION="git" if [[ "$ARCH" = "x86_64" ]] ; then APPIMAGE=$APP"-"$VERSION"-x86_64.AppImage" fi if [[ "$ARCH" = "i686" ]] ; then APPIMAGE=$APP"-"$VERSION"-i386.AppImage" fi echo $APPIMAGE mkdir -p /out rm -f /out/*.AppImage || true AppImageKit/AppImageAssistant.AppDir/package /kdevelop.appdir/ /out/$APPIMAGE chmod a+rwx /out/$APPIMAGE # So that we can edit the AppImage outside of the Docker container diff --git a/cmake/modules/FindClazyStandalone.cmake b/cmake/modules/FindClazyStandalone.cmake new file mode 100644 index 0000000000..aa22d6c5a5 --- /dev/null +++ b/cmake/modules/FindClazyStandalone.cmake @@ -0,0 +1,22 @@ +# Find the clazy-standalone executable +# +# Defines the following variables +# ClazyStandalone_EXECUTABLE - path of the clazy-standalone executable + +#============================================================================= +# Copyright 2018 Anton Anikin +# +# Distributed under the OSI-approved BSD License (the "License"); +# see accompanying file Copyright.txt for details. +# +# This software is distributed WITHOUT ANY WARRANTY; without even the +# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the License for more information. +#============================================================================= + +find_program(ClazyStandalone_EXECUTABLE NAMES clazy-standalone) + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(ClazyStandalone DEFAULT_MSG ClazyStandalone_EXECUTABLE) + +mark_as_advanced(ClazyStandalone_EXECUTABLE) diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index cd8a52a331..83ca20ac73 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -1,151 +1,152 @@ # BEGIN: Analyzers +add_subdirectory(clazy) add_subdirectory(cppcheck) if(UNIX AND NOT (APPLE OR CYGWIN)) add_subdirectory(heaptrack) endif() # END: Analyzers # BEGIN: Debuggers add_subdirectory(debuggercommon) add_subdirectory(lldb) add_subdirectory(gdb) # END: Debuggers # BEGIN: Documentation find_package(Qt5Help CONFIG) set_package_properties(Qt5Help PROPERTIES PURPOSE "The Help module for the Qt toolkit, needed for the qthelp plugin" URL "https://www.qt.io/" TYPE OPTIONAL) if(Qt5Help_FOUND) ecm_optional_add_subdirectory(qthelp) endif() ecm_optional_add_subdirectory(manpage) # END: Documentation # BEGIN: Formatters add_subdirectory(astyle) add_subdirectory(customscript) # END: Formatters # BEGIN: Languages ecm_optional_add_subdirectory(custom-definesandincludes) ecm_optional_add_subdirectory(qmljs) find_package(Clang 3.8) set(clangSearchHint "") if (NOT CLANG_FOUND) set(clangSearchHint "Please install a package providing libclang. Either pass -DLLVM_ROOT=/path/to/llvm-prefix or install the 'llvm-config' command-line utility for auto-detection.") endif() set_package_properties(Clang PROPERTIES DESCRIPTION "Clang libraries from the LLVM project. ${clangSearchHint}" PURPOSE "Used for KDevelop's C++/C support plugin." TYPE REQUIRED ) if (CLANG_FOUND) if (NOT CLANG_CLANG_LIB) message(FATAL_ERROR "Could not find the Clang C library: libclang") endif() add_library(Clang::clang UNKNOWN IMPORTED) set_property(TARGET Clang::clang PROPERTY IMPORTED_LOCATION ${CLANG_CLANG_LIB}) ecm_optional_add_subdirectory(clang) endif() # END: Languages # BEGIN: Project builders add_subdirectory(makebuilder) add_subdirectory(ninjabuilder) ecm_optional_add_subdirectory(cmakebuilder) if (KDevelop-PG-Qt_FOUND) ecm_optional_add_subdirectory(qmakebuilder) endif() # END: Project builders # BEGIN: Project managers ecm_optional_add_subdirectory(cmake) ecm_optional_add_subdirectory(custommake) ecm_optional_add_subdirectory(custom-buildsystem) if (KDevelop-PG-Qt_FOUND) ecm_optional_add_subdirectory(qmakemanager) endif() ecm_optional_add_subdirectory(genericprojectmanager) # END: Project managers # BEGIN: Runtimes add_subdirectory(android) if (UNIX) add_subdirectory(docker) add_subdirectory(flatpak) endif() # END: Runtimes # BEGIN: VCS ecm_optional_add_subdirectory(bazaar) ecm_optional_add_subdirectory(cvs) ecm_optional_add_subdirectory(git) ecm_optional_add_subdirectory(perforce) find_package(SubversionLibrary) set_package_properties(SubversionLibrary PROPERTIES PURPOSE "Support for Subversion integration" URL "http://subversion.tigris.org" TYPE OPTIONAL) if(SubversionLibrary_FOUND) ecm_optional_add_subdirectory(subversion) endif() add_subdirectory(vcschangesview) # END: VCS # BEGIN: Others add_subdirectory(appwizard) add_subdirectory(codeutils) add_subdirectory(contextbrowser) add_subdirectory(documentswitcher) add_subdirectory(documentview) add_subdirectory(execute) add_subdirectory(executescript) add_subdirectory(externalscript) add_subdirectory(filemanager) add_subdirectory(filetemplates) add_subdirectory(grepview) add_subdirectory(openwith) add_subdirectory(outlineview) add_subdirectory(patchreview) add_subdirectory(problemreporter) add_subdirectory(projectfilter) add_subdirectory(projectmanagerview) add_subdirectory(quickopen) add_subdirectory(sourceformatter) add_subdirectory(standardoutputview) add_subdirectory(switchtobuddy) add_subdirectory(testview) ecm_optional_add_subdirectory(classbrowser) ecm_optional_add_subdirectory(executeplasmoid) ecm_optional_add_subdirectory(ghprovider) ecm_optional_add_subdirectory(kdeprovider) ecm_optional_add_subdirectory(konsole) if (Qt5QuickWidgets_FOUND) add_subdirectory(welcomepage) endif() find_package(OktetaKastenControllers 0.3.1) set_package_properties(OktetaKastenControllers PROPERTIES PURPOSE "Required for building Okteta KDevelop plugin." URL "http://kde.org/" TYPE OPTIONAL) if (OktetaKastenControllers_FOUND) find_package( KastenControllers ) set_package_properties(KastenControllers PROPERTIES PURPOSE "Required for building Okteta KDevelop plugin." URL "http://kde.org/" TYPE OPTIONAL) endif() if (OktetaKastenControllers_FOUND AND KastenControllers_FOUND) add_subdirectory(okteta) endif() # END: Others diff --git a/plugins/clazy/CMakeLists.txt b/plugins/clazy/CMakeLists.txt new file mode 100644 index 0000000000..a53b3e70b5 --- /dev/null +++ b/plugins/clazy/CMakeLists.txt @@ -0,0 +1,68 @@ +add_definitions(-DTRANSLATION_DOMAIN=\"kdevclazy\") + +find_package(ClazyStandalone QUIET) +set_package_properties(ClazyStandalone PROPERTIES + DESCRIPTION "Qt oriented code checker based on clang framework. Krazy's little brother" + URL "https://commits.kde.org/clazy" + PURPOSE "Recommended: required by the non-essential Clazy plugin" + TYPE RUNTIME +) + +set(kdevclazy_core_SRCS + checksdb.cpp + job.cpp + jobparameters.cpp + utils.cpp +) +ecm_qt_declare_logging_category(kdevclazy_core_SRCS + HEADER debug.h + IDENTIFIER KDEV_CLAZY + CATEGORY_NAME "kdevelop.analyzers.clazy" +) +kconfig_add_kcfg_files(kdevclazy_core_SRCS GENERATE_MOC + config/globalsettings.kcfgc +) +kconfig_add_kcfg_files(kdevclazy_core_SRCS + config/projectsettings.kcfgc +) +add_library(kdevclazy_core STATIC + ${kdevclazy_core_SRCS} +) +target_link_libraries(kdevclazy_core + KDev::Project + KDev::Shell +) + +set(kdevclazy_SRCS + plugin.cpp + problemmodel.cpp + + config/checkswidget.cpp + config/commandlinewidget.cpp + config/globalconfigpage.cpp + config/projectconfigpage.cpp +) +ki18n_wrap_ui(kdevclazy_SRCS + config/checkswidget.ui + config/commandlinewidget.ui + config/globalconfigpage.ui + config/projectconfigpage.ui +) +qt5_add_resources(kdevclazy_SRCS + kdevclazy.qrc +) +kdevplatform_add_plugin(kdevclazy + JSON kdevclazy.json + SOURCES ${kdevclazy_SRCS} +) +target_link_libraries(kdevclazy + kdevclazy_core + KF5::ItemViews +) + +ecm_install_icons(ICONS icons/128-apps-clazy.png + DESTINATION ${KDE_INSTALL_ICONDIR} THEME hicolor) + +if(BUILD_TESTING) + add_subdirectory(tests) +endif() diff --git a/plugins/clazy/Messages.sh b/plugins/clazy/Messages.sh new file mode 100644 index 0000000000..33e4605dc8 --- /dev/null +++ b/plugins/clazy/Messages.sh @@ -0,0 +1,4 @@ +#!/bin/sh +$EXTRACTRC `find . -name \*.rc` `find . -name \*.ui` >>rc.cpp +$XGETTEXT `find . -name \*.cc -o -name \*.cpp -o -name \*.h` -o $podir/kdevclazy.pot +rm -f rc.cpp diff --git a/plugins/clazy/README.txt b/plugins/clazy/README.txt new file mode 100644 index 0000000000..4bfa6c93e6 --- /dev/null +++ b/plugins/clazy/README.txt @@ -0,0 +1,16 @@ +This plugin integrates Clazy to KDevelop. + +Clazy is a compiler plugin which allows clang to understand Qt semantics. You get more than 50 Qt related compiler warnings, ranging from unneeded memory allocations to misusage of API, including fix-its for automatic refactoring. + +https://commits.kde.org/clazy + +The plugin allows you to check project's code with clazy checker. Runtime dependencies: + +* clazy-standalone (clazy part) +* installed clazy docs (used to building checks DB with errors descriptions) +* make (analysis run, see later) +* compile_commands.json present in project's build directory + +Plugin's GUI provides easy way to clazy configuration, enabling/disabling checks and so on. + +Analysis run done through executing system make command with custom makefile which generates by the plugin in the project's build directory. Such approach allows us to enable/disable parallel code analysis with simple setup of standard make "-j" parameter. Current makefile structure assumes that each checked source file is independent from all others so we can efficiently load all present CPUs - scalability is close to linear. diff --git a/plugins/clazy/checksdb.cpp b/plugins/clazy/checksdb.cpp new file mode 100644 index 0000000000..ac2d849975 --- /dev/null +++ b/plugins/clazy/checksdb.cpp @@ -0,0 +1,156 @@ +/* This file is part of KDevelop + + Copyright 2018 Anton Anikin + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#include "checksdb.h" + +#include "globalsettings.h" +#include "utils.h" + +#include + +#include +#include + +namespace Clazy +{ + +ChecksDB::ChecksDB(const QUrl& docsPath) +{ + static const QHash levelName = { + { QStringLiteral("manuallevel"), QStringLiteral("manual") }, + { QStringLiteral("hiddenlevel"), QStringLiteral("manual") } + }; + + static const QHash levelDisplayName = { + { QStringLiteral("level0"), i18n("Level 0") }, + { QStringLiteral("level1"), i18n("Level 1") }, + { QStringLiteral("level2"), i18n("Level 2") }, + { QStringLiteral("level3"), i18n("Level 3") }, + { QStringLiteral("manual"), i18n("Manual Level") } + }; + + static const QHash levelDescription = { + { QStringLiteral("level0"), + i18n("Very stable checks, 99.99% safe, mostly no false-positives, very desirable.") }, + + { QStringLiteral("level1"), + i18n("The default level. Very similar to level 0, slightly more false-positives but very few.") }, + + { QStringLiteral("level2"), + i18n("Also very few false-positives, but contains noisy checks which not everyone agree should be default.") }, + + { QStringLiteral("level3"), + i18n("Contains checks with high rate of false-positives.") }, + + { QStringLiteral("manual"), + i18n("Checks here need to be enabled explicitly, as they don't belong to any level. " + "Checks here are very stable and have very few false-positives.") } + }; + + const QString defaultError = i18n( + "Unable to load Clazy checks information from '%1'. Please check your settings.", + docsPath.toLocalFile()); + + QDir docsDir(docsPath.toLocalFile()); + if (!docsDir.exists()) { + m_error = defaultError; + return; + } + + const QRegularExpression levelRE(QStringLiteral(".*level.*")); + const QRegularExpression checkRE(QStringLiteral("^README-(.+)\\.md$")); + + const auto levelsDirs = docsDir.entryList(QDir::Dirs | QDir::NoDotAndDotDot); + for (const auto& levelDir : levelsDirs) { + if (!levelRE.match(levelDir).hasMatch()) { + continue; + } + + if (!docsDir.cd(levelDir)) { + continue; + } + + auto level = new Level; + level->name = levelName.value(levelDir, levelDir); + level->displayName = levelDisplayName.value(level->name, levelDir); + level->description = levelDescription.value(level->name, {}); + + const auto checksFiles = docsDir.entryList(QDir::Files | QDir::Readable); + for (const auto& checkFile : checksFiles) { + auto match = checkRE.match(checkFile); + if (!match.hasMatch()) { + continue; + } + + QFile mdFile(docsDir.absoluteFilePath(checkFile)); + if (!mdFile.open(QIODevice::ReadOnly)) { + continue; + } + + auto check = new Check; + check->level = level; + check->name = match.captured(1); + check->description = markdown2html(mdFile.readAll()); + level->checks[check->name] = check; + + m_checks[check->name] = check; + } + + if (level->checks.isEmpty()) { + delete level; + } else { + m_levels[level->name] = level; + } + + docsDir.cdUp(); + } + + if (m_levels.isEmpty()) { + m_error = defaultError; + } +} + +ChecksDB::~ChecksDB() +{ + qDeleteAll(m_levels); + qDeleteAll(m_checks); +} + +bool ChecksDB::isValid() const +{ + return m_error.isEmpty(); +} + +QString ChecksDB::error() const +{ + return m_error; +} + +const QMap& ChecksDB::levels() const +{ + return m_levels; +} + +const QMap& ChecksDB::checks() const +{ + return m_checks; +} + +} diff --git a/plugins/clazy/checksdb.h b/plugins/clazy/checksdb.h new file mode 100644 index 0000000000..2900aae26a --- /dev/null +++ b/plugins/clazy/checksdb.h @@ -0,0 +1,70 @@ +/* This file is part of KDevelop + + Copyright 2018 Anton Anikin + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#ifndef KDEVCLAZY_CHECKS_DB_H +#define KDEVCLAZY_CHECKS_DB_H + +#include +#include + +namespace Clazy +{ + +struct Level; + +struct Check +{ + const Level* level = nullptr; + QString name; + QString description; +}; + +struct Level +{ + QString name; + QString displayName; + QString description; + + QMap checks; +}; + +class ChecksDB +{ +public: + explicit ChecksDB(const QUrl& docsPath); + ~ChecksDB(); + + bool isValid() const; + QString error() const; + + const QMap& levels() const; + + const QMap& checks() const; + +private: + QString m_error; + + QMap m_checks; + QMap m_levels; +}; + +} + +#endif diff --git a/plugins/clazy/config/checkswidget.cpp b/plugins/clazy/config/checkswidget.cpp new file mode 100644 index 0000000000..de8141a4bd --- /dev/null +++ b/plugins/clazy/config/checkswidget.cpp @@ -0,0 +1,298 @@ +/* This file is part of KDevelop + + Copyright 2018 Anton Anikin + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#include "checkswidget.h" +#include "ui_checkswidget.h" + +#include "checksdb.h" +#include "debug.h" + +#include + +#include + +namespace Clazy +{ + +enum DataRole { + CheckRole = Qt::UserRole + 1, + DescriptionRole = Qt::UserRole + 2 +}; + +enum ItemType { + LevelType, + CheckType +}; + +ChecksWidget::ChecksWidget(QSharedPointer db, QWidget* parent) + : QWidget(parent) + , m_ui(new Ui::ChecksWidget) +{ + m_ui->setupUi(this); + + m_ui->filterEdit->addTreeWidget(m_ui->checksTree); + m_ui->filterEdit->setPlaceholderText(i18n("Search checks...")); + connect(m_ui->filterEdit, &KTreeWidgetSearchLine::searchUpdated, this, &ChecksWidget::searchUpdated); + + auto resetMenu = new QMenu(this); + m_ui->resetButton->setMenu(resetMenu); + + for (auto level : db->levels()) { + auto levelItem = new QTreeWidgetItem(m_ui->checksTree, { level->displayName }, LevelType); + levelItem->setData(0, CheckRole, level->name); + levelItem->setData(0, DescriptionRole, level->description); + levelItem->setCheckState(0, Qt::Unchecked); + + m_items[level->name] = levelItem; + + auto levelAction = resetMenu->addAction(level->displayName); + connect(levelAction, &QAction::triggered, this, [this, level, levelItem]() { + { + // Block QLineEdit::textChanged() signal, which is used by KTreeWidgetSearchLine to + // start delayed search. + QSignalBlocker blocker(m_ui->filterEdit); + m_ui->filterEdit->clear(); + } + m_ui->filterEdit->updateSearch(); + + setChecks(level->name); + m_ui->checksTree->setCurrentItem(levelItem); + }); + + for (auto check : qAsConst(level->checks)) { + auto checkItem = new QTreeWidgetItem(levelItem, { check->name }, CheckType); + checkItem->setData(0, CheckRole, check->name); + checkItem->setData(0, DescriptionRole, check->description); + checkItem->setCheckState(0, Qt::Unchecked); + + m_items[check->name] = checkItem; + } + } + + connect(m_ui->checksTree, &QTreeWidget::itemChanged, this, [this](QTreeWidgetItem* item) { + setState(item, item->checkState(0)); + updateChecks(); + }); + + connect(m_ui->checksTree, &QTreeWidget::currentItemChanged, this, [this, db](QTreeWidgetItem* current) { + if (current) { + m_ui->descriptionView->setText(current->data(0, DescriptionRole).toString()); + } else { + m_ui->descriptionView->clear(); + } + }); +} + +ChecksWidget::~ChecksWidget() = default; + +QString ChecksWidget::checks() const +{ + return m_checks; +} + +void ChecksWidget::setChecks(const QString& checks) +{ + if (m_checks == checks) { + return; + } + + // Clear all selections + for (int i = 0 ; i < m_ui->checksTree->topLevelItemCount(); ++i) { + setState(m_ui->checksTree->topLevelItem(i), Qt::Unchecked); + } + + const QStringList checksList = checks.split(',', QString::SkipEmptyParts); + for (auto checkName : checksList) { + checkName = checkName.trimmed(); + if (checkName == QStringLiteral("manual")) { + continue; + } + + auto state = Qt::Checked; + if (checkName.startsWith(QStringLiteral("no-"))) { + checkName = checkName.mid(3); + state = Qt::Unchecked; + } + + if (auto checkItem = m_items.value(checkName, nullptr)) { + setState(checkItem, state); + } + } + + updateChecks(); + m_ui->checksTree->setCurrentItem(nullptr); +} + +QStringList levelChecks( + const QTreeWidget* checksTree, + const QString& levelName, + const QList& levelItems) +{ + QStringList checksList; + if (!levelName.isEmpty()) { + checksList += levelName; + } + + for (int i = 0; i < checksTree->topLevelItemCount(); ++i) { + const auto levelItem = checksTree->topLevelItem(i); + const bool insideLevel = levelItems.contains(levelItem); + + for (int j = 0; j < levelItem->childCount(); ++j) { + auto checkItem = levelItem->child(j); + auto checkName = checkItem->data(0, CheckRole).toString(); + + if (insideLevel) { + if (checkItem->checkState(0) == Qt::Unchecked) { + checksList += QStringLiteral("no-%1").arg(checkName); + } + } else { + if (checkItem->checkState(0) == Qt::Checked) { + checksList += checkName; + } + } + } + } + + return checksList; +} + +void ChecksWidget::updateChecks() +{ + QStringList checksList; + QList levelItems; + + // Here we try to find "best" (shortest) checks representation. To do this we build checks list + // for every level and test it's size. + for (int i = 0; i < m_ui->checksTree->topLevelItemCount(); ++i) { + auto levelItem = m_ui->checksTree->topLevelItem(i); + auto levelName = levelItem->data(0, CheckRole).toString(); + + if (levelName == QStringLiteral("manual")) { + // Manual level is "fake level" so we clear the name and will store only + // selected checks. + levelItems.clear(); + levelName.clear(); + } else { + levelItems += levelItem; + } + + auto levelList = levelChecks(m_ui->checksTree, levelName, levelItems); + if (checksList.isEmpty() || checksList.size() > levelList.size()) { + checksList = levelList; + } + } + + m_ui->messageLabel->setVisible(checksList.isEmpty()); + + auto checks = checksList.join(QLatin1Char(',')); + if (m_checks != checks) { + m_checks = checks; + emit checksChanged(m_checks); + } +} + +void ChecksWidget::setState(QTreeWidgetItem* item, Qt::CheckState state, bool force) +{ + Q_ASSERT(item); + + QSignalBlocker blocker(m_ui->checksTree); + + if (item->type() == LevelType) { + if (state == Qt::Checked) { + // When we enable some non-manual level item, we should also try to enable all + // upper level items. We enable upper item only when it's state is Qt::Unchecked. + // If the state is Qt::PartiallyChecked we assume that it was configured earlier and + // we should skip the item to keep user's checks selection. + const int index = m_ui->checksTree->indexOfTopLevelItem(item); + if (index > 0 && index < (m_ui->checksTree->topLevelItemCount() - 1)) { + setState(m_ui->checksTree->topLevelItem(index - 1), state, false); + } + + if (item->checkState(0) != Qt::Unchecked && !force) { + return; + } + } + + item->setCheckState(0, state); + if (state != Qt::PartiallyChecked) { + for (int i = 0; i < item->childCount(); ++i) { + item->child(i)->setCheckState(0, state); + } + } + return; + } + + item->setCheckState(0, state); + + auto levelItem = item->parent(); + Q_ASSERT(levelItem); + + const int childCount = levelItem->childCount(); + int checkedCount = 0; + + for (int i = 0; i < childCount; ++i) { + if (levelItem->child(i)->checkState(0) == Qt::Checked) { + ++checkedCount; + } + } + + if (checkedCount == 0) { + setState(levelItem, Qt::Unchecked); + } else if (checkedCount == childCount) { + setState(levelItem, Qt::Checked); + } else { + setState(levelItem, Qt::PartiallyChecked); + } +} + +void ChecksWidget::searchUpdated(const QString& searchString) +{ + if (searchString.isEmpty()) { + m_ui->checksTree->collapseAll(); + m_ui->checksTree->setCurrentItem(nullptr); + return; + } + + m_ui->checksTree->expandAll(); + + QTreeWidgetItem* firstVisibleLevel = nullptr; + for (int i = 0; i < m_ui->checksTree->topLevelItemCount(); ++i) { + auto levelItem = m_ui->checksTree->topLevelItem(i); + if (levelItem->isHidden()) { + continue; + } + + if (!firstVisibleLevel) { + firstVisibleLevel = levelItem; + } + + for (int j = 0; j < levelItem->childCount(); ++j) { + auto checkItem = levelItem->child(j); + if (!checkItem->isHidden()) { + m_ui->checksTree->setCurrentItem(checkItem); + return; + } + } + } + + m_ui->checksTree->setCurrentItem(firstVisibleLevel); +} + +} diff --git a/plugins/clazy/config/checkswidget.h b/plugins/clazy/config/checkswidget.h new file mode 100644 index 0000000000..b06644c3a0 --- /dev/null +++ b/plugins/clazy/config/checkswidget.h @@ -0,0 +1,72 @@ +/* This file is part of KDevelop + + Copyright 2018 Anton Anikin + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#ifndef KDEVCLAZY_CHECKS_WIDGET_H +#define KDEVCLAZY_CHECKS_WIDGET_H + +#include + +class QTreeWidget; +class QTreeWidgetItem; + +namespace Clazy +{ + +namespace Ui { class ChecksWidget; } + +class ChecksDB; + +class ChecksWidget : public QWidget +{ + Q_OBJECT + + Q_PROPERTY( + QString checks + READ checks + WRITE setChecks + NOTIFY checksChanged + USER true) + +public: + explicit ChecksWidget(QSharedPointer db, QWidget* parent = nullptr); + ~ChecksWidget() override; + + QString checks() const; + + void setChecks(const QString& checks); + +Q_SIGNALS: + void checksChanged(const QString& checks); + +private: + void updateChecks(); + void setState(QTreeWidgetItem* item, Qt::CheckState state, bool force = true); + void searchUpdated(const QString& searchString); + +private: + QScopedPointer m_ui; + + QString m_checks; + QHash m_items; +}; + +} + +#endif diff --git a/plugins/clazy/config/checkswidget.ui b/plugins/clazy/config/checkswidget.ui new file mode 100644 index 0000000000..1601a5940f --- /dev/null +++ b/plugins/clazy/config/checkswidget.ui @@ -0,0 +1,97 @@ + + + Clazy::ChecksWidget + + + + 0 + 0 + 606 + 284 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + Reset checks + + + + + + + + + + + + + 0 + 0 + + + + Qt::Horizontal + + + false + + + + false + + + + 1 + + + + + + true + + + + + + + + Since nothing is selected Clazy will use all checks from levels 0 and 1. + + + false + + + + + + + + KMessageWidget + QFrame +
kmessagewidget.h
+
+ + KTreeWidgetSearchLine + QLineEdit +
ktreewidgetsearchline.h
+
+
+ + +
diff --git a/plugins/clazy/config/commandlinewidget.cpp b/plugins/clazy/config/commandlinewidget.cpp new file mode 100644 index 0000000000..e501d2d79d --- /dev/null +++ b/plugins/clazy/config/commandlinewidget.cpp @@ -0,0 +1,76 @@ +/* This file is part of KDevelop + + Copyright 2018 Anton Anikin + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#include "commandlinewidget.h" +#include "ui_commandlinewidget.h" + +#include +#include + +namespace Clazy +{ + +CommandLineWidget::CommandLineWidget(QWidget* parent) + : QWidget(parent) + , m_ui(new Ui::CommandLineWidget) +{ + m_ui->setupUi(this); + m_ui->cmdEdit->setFont(QFontDatabase::systemFont(QFontDatabase::FixedFont)); + + connect(m_ui->cmdFilter->lineEdit(), &QLineEdit::textChanged, this, &CommandLineWidget::updateCommandLine); + connect(m_ui->cmdBreak, &QCheckBox::stateChanged, this, &CommandLineWidget::updateCommandLine); +} + +CommandLineWidget::~CommandLineWidget() = default; + +void CommandLineWidget::setText(const QString& text) +{ + if (m_text != text) { + m_text = text; + updateCommandLine(); + } +} + +void CommandLineWidget::updateCommandLine() +{ + auto commandLine = m_text; + if (m_ui->cmdBreak->isChecked()) { + commandLine.replace(QLatin1String(" -"), QLatin1String("\n-")); + commandLine.replace(QLatin1String(","), QLatin1String("\n,")); + } + + auto filterText = m_ui->cmdFilter->lineEdit()->text(); + if (!filterText.isEmpty()) { + QStringList lines = commandLine.split('\n'); + QMutableStringListIterator i(lines); + + while (i.hasNext()) { + if (!i.next().contains(filterText)) { + i.remove(); + } + } + + commandLine = lines.join(QLatin1Char('\n')); + } + + m_ui->cmdEdit->setPlainText(commandLine); +} + +} diff --git a/plugins/clazy/config/commandlinewidget.h b/plugins/clazy/config/commandlinewidget.h new file mode 100644 index 0000000000..c419190aff --- /dev/null +++ b/plugins/clazy/config/commandlinewidget.h @@ -0,0 +1,52 @@ +/* This file is part of KDevelop + + Copyright 2018 Anton Anikin + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#ifndef KDEVCLAZY_COMMAND_LINE_WIDGET_H +#define KDEVCLAZY_COMMAND_LINE_WIDGET_H + +#include + +namespace Clazy +{ + +namespace Ui { class CommandLineWidget; } + +class CommandLineWidget : public QWidget +{ + Q_OBJECT + +public: + explicit CommandLineWidget(QWidget* parent = nullptr); + ~CommandLineWidget() override; + +public Q_SLOTS: + void setText(const QString& text); + +private: + void updateCommandLine(); + +private: + QScopedPointer m_ui; + QString m_text; +}; + +} + +#endif diff --git a/plugins/clazy/config/commandlinewidget.ui b/plugins/clazy/config/commandlinewidget.ui new file mode 100644 index 0000000000..c2241b2174 --- /dev/null +++ b/plugins/clazy/config/commandlinewidget.ui @@ -0,0 +1,67 @@ + + + Clazy::CommandLineWidget + + + + 0 + 0 + 400 + 300 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Command Line + + + + + + + + + + + Break lines + + + + + + + + + true + + + + + + + + + + + KFilterProxySearchLine + QWidget +
kfilterproxysearchline.h
+
+
+ + +
diff --git a/plugins/clazy/config/globalconfigpage.cpp b/plugins/clazy/config/globalconfigpage.cpp new file mode 100644 index 0000000000..2449de3394 --- /dev/null +++ b/plugins/clazy/config/globalconfigpage.cpp @@ -0,0 +1,99 @@ +/* This file is part of KDevelop + + Copyright 2018 Anton Anikin + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#include "globalconfigpage.h" +#include "ui_globalconfigpage.h" + +#include "checksdb.h" +#include "globalsettings.h" + +namespace Clazy +{ + +GlobalConfigPage::GlobalConfigPage(KDevelop::IPlugin* plugin, QWidget* parent) + : ConfigPage(plugin, GlobalSettings::self(), parent) +{ + Ui::GlobalConfigPage ui; + ui.setupUi(this); + + auto checkPaths = [ui]() { + ChecksDB db(ui.kcfg_docsPath->url()); + ui.checksInfoLabel->setText(i18np("1 check detected", "%1 checks detected", db.checks().size())); + + JobGlobalParameters params(ui.kcfg_executablePath->url(), ui.kcfg_docsPath->url()); + if (!params.isValid()) { + ui.errorWidget->setText(params.error()); + ui.errorWidget->setVisible(true); + return; + } + + if (!db.isValid()) { + ui.errorWidget->setText(db.error()); + ui.errorWidget->setVisible(true); + return; + } + + ui.errorWidget->setVisible(false); + }; + + connect(ui.kcfg_executablePath, &KUrlRequester::textChanged, this, checkPaths); + connect(ui.kcfg_docsPath, &KUrlRequester::textChanged, this, checkPaths); + + checkPaths(); + ui.kcfg_executablePath->setPlaceholderText(ui.kcfg_executablePath->toolTip()); + ui.kcfg_docsPath->setPlaceholderText(ui.kcfg_docsPath->toolTip()); + + auto checkJobs = [ui]() { + const bool jobsEnabled = ui.kcfg_parallelJobsEnabled->checkState() == Qt::Checked; + const bool autoEnabled = ui.kcfg_parallelJobsAutoCount->checkState() == Qt::Checked; + + ui.kcfg_parallelJobsAutoCount->setEnabled(jobsEnabled); + + ui.kcfg_parallelJobsFixedCount->setEnabled(jobsEnabled && !autoEnabled); + ui.parallelJobsFixedCountLabel->setEnabled(jobsEnabled && !autoEnabled); + }; + + connect(ui.kcfg_parallelJobsEnabled, &QCheckBox::stateChanged, this, checkJobs); + connect(ui.kcfg_parallelJobsAutoCount, &QCheckBox::stateChanged, this, checkJobs); + + checkJobs(); +} + +KDevelop::ConfigPage::ConfigPageType GlobalConfigPage::configPageType() const +{ + return KDevelop::ConfigPage::AnalyzerConfigPage; +} + +QString GlobalConfigPage::name() const +{ + return i18n("Clazy"); +} + +QString GlobalConfigPage::fullName() const +{ + return i18n("Configure Clazy Settings"); +} + +QIcon GlobalConfigPage::icon() const +{ + return QIcon::fromTheme(QStringLiteral("clazy")); +} + +} diff --git a/plugins/clazy/config/globalconfigpage.h b/plugins/clazy/config/globalconfigpage.h new file mode 100644 index 0000000000..818c82fa1c --- /dev/null +++ b/plugins/clazy/config/globalconfigpage.h @@ -0,0 +1,46 @@ +/* This file is part of KDevelop + + Copyright 2018 Anton Anikin + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#ifndef KDEVCLAZY_GLOBAL_CONFIG_PAGE_H +#define KDEVCLAZY_GLOBAL_CONFIG_PAGE_H + +#include + +namespace Clazy +{ + +class GlobalConfigPage: public KDevelop::ConfigPage +{ + Q_OBJECT + +public: + GlobalConfigPage(KDevelop::IPlugin* plugin, QWidget* parent); + ~GlobalConfigPage() override = default; + + KDevelop::ConfigPage::ConfigPageType configPageType() const override; + + QString name() const override; + QString fullName() const override; + QIcon icon() const override; +}; + +} + +#endif diff --git a/plugins/clazy/config/globalconfigpage.ui b/plugins/clazy/config/globalconfigpage.ui new file mode 100644 index 0000000000..df52e8b301 --- /dev/null +++ b/plugins/clazy/config/globalconfigpage.ui @@ -0,0 +1,198 @@ + + + Clazy::GlobalConfigPage + + + + 0 + 0 + 568 + 670 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Paths + + + + + + clazy-standalone: + + + + + + + Path to clazy-standalone executable. + + + KFile::ExistingOnly|KFile::File|KFile::LocalOnly + + + + + + + Documentation: + + + + + + + Path to clazy documentation directory. + + + KFile::Directory|KFile::ExistingOnly|KFile::LocalOnly + + + + + + + checksInfoLabel + + + + + + + + + + + + + + + + Run analysis jobs in parallel + + + + + + + + + Use all CPU cores + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Maximum number of threads: + + + + + + + 1 + + + + + + + + + + + + Output + + + + + + Hide output view during check + + + + + + + Verbose output + + + + + + + + + + errorWidget + + + true + + + false + + + KMessageWidget::Error + + + + + + + Qt::Vertical + + + + 20 + 264 + + + + + + + + + KMessageWidget + QFrame +
kmessagewidget.h
+
+ + KUrlRequester + QWidget +
kurlrequester.h
+
+
+ + +
diff --git a/plugins/clazy/config/globalsettings.kcfg b/plugins/clazy/config/globalsettings.kcfg new file mode 100644 index 0000000000..b6be5eb12d --- /dev/null +++ b/plugins/clazy/config/globalsettings.kcfg @@ -0,0 +1,43 @@ + + + + + + "jobparameters.h" + + + + + JobGlobalParameters::defaultExecutablePath() + + + + JobGlobalParameters::defaultDocsPath() + + + + + true + + + + true + + + + 2 + + + + true + + + + false + + + + diff --git a/plugins/clazy/config/globalsettings.kcfgc b/plugins/clazy/config/globalsettings.kcfgc new file mode 100644 index 0000000000..b841e7f1be --- /dev/null +++ b/plugins/clazy/config/globalsettings.kcfgc @@ -0,0 +1,4 @@ +File=globalsettings.kcfg +NameSpace=Clazy +ClassName=GlobalSettings +Singleton=true diff --git a/plugins/clazy/config/projectconfigpage.cpp b/plugins/clazy/config/projectconfigpage.cpp new file mode 100644 index 0000000000..f162165648 --- /dev/null +++ b/plugins/clazy/config/projectconfigpage.cpp @@ -0,0 +1,138 @@ +/* This file is part of KDevelop + + Copyright 2018 Anton Anikin + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#include "projectconfigpage.h" +#include "ui_projectconfigpage.h" + +#include "checksdb.h" +#include "checkswidget.h" +#include "plugin.h" +#include "projectsettings.h" + +#include + +#include + +namespace Clazy +{ + +ProjectConfigPage::ProjectConfigPage(Plugin* plugin, KDevelop::IProject* project, QWidget* parent) + : ConfigPage(plugin, new ProjectSettings, parent) + , m_parameters(new JobParameters(project)) +{ + Q_ASSERT(plugin); + + Ui::ProjectConfigPage ui; + ui.setupUi(this); + + if (plugin->checksDB()->isValid()) { + ui.dbError->setVisible(false); + } else { + ui.dbError->setText(plugin->checksDB()->error()); + ui.dbError->setVisible(true); + + ui.tabWidget->setVisible(false); + ui.commandLineWidget->setVisible(false); + return; + } + + configSkeleton()->setSharedConfig(project->projectConfiguration()); + configSkeleton()->load(); + + // ============================================================================================= + + auto checksWidget = new ChecksWidget(plugin->checksDB()); + checksWidget->setObjectName(QStringLiteral("kcfg_checks")); + connect(checksWidget, &ChecksWidget::checksChanged, m_parameters.data(), &JobParameters::setChecks); + + auto checksLayout = new QVBoxLayout(ui.checksTab); + checksLayout->addWidget(checksWidget); + + // ============================================================================================= + + connect(ui.kcfg_onlyQt, &QCheckBox::stateChanged, this, [this](int state) { + m_parameters->setOnlyQt(state != Qt::Unchecked); + }); + + connect(ui.kcfg_qtDeveloper, &QCheckBox::stateChanged, this, [this](int state) { + m_parameters->setQtDeveloper(state != Qt::Unchecked); + }); + + connect(ui.kcfg_qt4Compat, &QCheckBox::stateChanged, this, [this](int state) { + m_parameters->setQt4Compat(state != Qt::Unchecked); + }); + + connect(ui.kcfg_visitImplicitCode, &QCheckBox::stateChanged, this, [this](int state) { + m_parameters->setVisitImplicitCode(state != Qt::Unchecked); + }); + + connect(ui.kcfg_ignoreIncludedFiles, &QCheckBox::stateChanged, this, [this](int state) { + m_parameters->setIgnoreIncludedFiles(state != Qt::Unchecked); + }); + + ui.kcfg_headerFilter->setPlaceholderText(ui.kcfg_headerFilter->toolTip()); + connect(ui.kcfg_headerFilter, &QLineEdit::textChanged, + m_parameters.data(), &JobParameters::setHeaderFilter); + + connect(ui.kcfg_enableAllFixits, &QCheckBox::stateChanged, this, [this](int state) { + m_parameters->setEnableAllFixits(state != Qt::Unchecked); + }); + + connect(ui.kcfg_noInplaceFixits, &QCheckBox::stateChanged, this, [this](int state) { + m_parameters->setNoInplaceFixits(state != Qt::Unchecked); + }); + + // ============================================================================================= + + ui.kcfg_extraAppend->setPlaceholderText(ui.kcfg_extraAppend->toolTip()); + connect(ui.kcfg_extraAppend, &QLineEdit::textChanged, + m_parameters.data(), &JobParameters::setExtraAppend); + + ui.kcfg_extraPrepend->setPlaceholderText(ui.kcfg_extraPrepend->toolTip()); + connect(ui.kcfg_extraPrepend, &QLineEdit::textChanged, + m_parameters.data(), &JobParameters::setExtraPrepend); + + ui.kcfg_extraClazy->setPlaceholderText(ui.kcfg_extraClazy->toolTip()); + connect(ui.kcfg_extraClazy, &QLineEdit::textChanged, + m_parameters.data(), &JobParameters::setExtraClazy); + + // ============================================================================================= + + auto updateCommandLine = [this, ui]() { + ui.commandLineWidget->setText(m_parameters->commandLine().join(QLatin1Char(' '))); + }; + + connect(m_parameters.data(), &JobParameters::changed, this, updateCommandLine); + updateCommandLine(); +} + +ProjectConfigPage::~ProjectConfigPage() = default; + +QIcon ProjectConfigPage::icon() const +{ + return QIcon::fromTheme(QStringLiteral("clazy")); +} + +QString ProjectConfigPage::name() const +{ + return i18n("Clazy"); +} + +} diff --git a/plugins/clazy/config/projectconfigpage.h b/plugins/clazy/config/projectconfigpage.h new file mode 100644 index 0000000000..ef706f2818 --- /dev/null +++ b/plugins/clazy/config/projectconfigpage.h @@ -0,0 +1,51 @@ +/* This file is part of KDevelop + + Copyright 2018 Anton Anikin + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#ifndef KDEVCLAZY_PROJECT_CONFIG_PAGE_H +#define KDEVCLAZY_PROJECT_CONFIG_PAGE_H + +#include + +namespace KDevelop { class IProject; } + +namespace Clazy +{ + +class JobParameters; +class Plugin; + +class ProjectConfigPage : public KDevelop::ConfigPage +{ + Q_OBJECT + +public: + ProjectConfigPage(Plugin* plugin, KDevelop::IProject* project, QWidget* parent); + ~ProjectConfigPage() override; + + QIcon icon() const override; + QString name() const override; + +private: + QScopedPointer m_parameters; +}; + +} + +#endif diff --git a/plugins/clazy/config/projectconfigpage.ui b/plugins/clazy/config/projectconfigpage.ui new file mode 100644 index 0000000000..40ac59b8fa --- /dev/null +++ b/plugins/clazy/config/projectconfigpage.ui @@ -0,0 +1,281 @@ + + + Clazy::ProjectConfigPage + + + + 0 + 0 + 723 + 396 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + 0 + + + + 0 + + + + Checks + + + + + Options + + + + + + + + + + + + Do not emit warnings for non-Qt files, or in other words, if -DQT_CORE_LIB is missing. + + + Only Qt + + + + + + + Disable checks not compatible with Qt 4. + + + Qt4 compatible + + + + + + + For running clazy on Qt itself, optional, but honours specific guidelines. + + + Qt developer + + + + + + + For visiting implicit code like compiler generated constructors. +None of the built-in checks benefit from this, but can be useful for custom checks. + + + Visit implicit code + + + + + + + + + + + + + + + + Only emit warnings for the current file being compiled and ignore any includes. +Useful for performance reasons. + + + Ignore included files + + + + + + + + + Header filter: + + + + + + + Regular expression matching the names of the headers to output diagnostics from. +Diagnostics from the main file of each translation unit are always displayed. + + + true + + + + + + + + + + + + + + + + + + + + Enable all fixits + + + + + + + Fixits will be applied to a separate file (for unit-test use only). + + + No-inplace fixits + + + + + + + + + Please backup your code before fixits applying. For better results also disable +parallel checking, which can lead to multiple fixit applying for header files. + + + false + + + KMessageWidget::Warning + + + + + + + + + + + Extra Parameters + + + + + + Compiler append: + + + + + + + Additional parameters to append to the compiler command line. + + + true + + + + + + + Compiler prepend: + + + + + + + Additional parameters to prepend to the compiler command line. + + + true + + + + + + + Additional parameters to clazy-standalone. + + + true + + + + + + + Extra parameters: + + + + + + + + + + + + + + dbError + + + true + + + false + + + KMessageWidget::Error + + + + + + + + KMessageWidget + QFrame +
kmessagewidget.h
+
+ + CommandLineWidget + QWidget +
config/commandlinewidget.h
+ 1 +
+
+ + +
diff --git a/plugins/clazy/config/projectsettings.kcfg b/plugins/clazy/config/projectsettings.kcfg new file mode 100644 index 0000000000..e0ad99f10a --- /dev/null +++ b/plugins/clazy/config/projectsettings.kcfg @@ -0,0 +1,52 @@ + + + + "jobparameters.h" + + + + + JobParameters::defaultChecks() + + + + false + + + + false + + + + false + + + + false + + + + false + + + + + + false + + + + false + + + + + + + + + + diff --git a/plugins/clazy/config/projectsettings.kcfgc b/plugins/clazy/config/projectsettings.kcfgc new file mode 100644 index 0000000000..f0e6f65c4e --- /dev/null +++ b/plugins/clazy/config/projectsettings.kcfgc @@ -0,0 +1,3 @@ +File=projectsettings.kcfg +NameSpace=Clazy +ClassName=ProjectSettings diff --git a/plugins/clazy/icons/128-apps-clazy.png b/plugins/clazy/icons/128-apps-clazy.png new file mode 100644 index 0000000000..ecff717e26 Binary files /dev/null and b/plugins/clazy/icons/128-apps-clazy.png differ diff --git a/plugins/clazy/job.cpp b/plugins/clazy/job.cpp new file mode 100644 index 0000000000..2fc47d57f1 --- /dev/null +++ b/plugins/clazy/job.cpp @@ -0,0 +1,276 @@ +/* This file is part of KDevelop + + Copyright 2018 Anton Anikin + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#include "job.h" + +#include "checksdb.h" +#include "debug.h" +#include "globalsettings.h" +#include "plugin.h" +#include "utils.h" + +#include +#include + +#include +#include + +#include +#include +#include +#include +#include + +namespace Clazy +{ + +Job::Job() + : KDevelop::OutputExecuteJob(nullptr) +{ +} + +Job::Job(const JobParameters& params, QSharedPointer db) + : KDevelop::OutputExecuteJob(nullptr) + , m_db(db) + , m_timer(new QElapsedTimer) +{ + setJobName(i18n("Clazy Analysis (%1)", prettyPathName(params.checkPath()))); + + setCapabilities(KJob::Killable); + setStandardToolView(KDevelop::IOutputView::TestView); + setBehaviours(KDevelop::IOutputView::AutoScroll); + + setProperties(OutputExecuteJob::JobProperty::DisplayStdout); + setProperties(OutputExecuteJob::JobProperty::DisplayStderr); + setProperties(OutputExecuteJob::JobProperty::PostProcessOutput); + + *this << QStringLiteral("make"); + + if (GlobalSettings::parallelJobsEnabled()) { + const int threadsCount = + GlobalSettings::parallelJobsAutoCount() ? + QThread::idealThreadCount() : + GlobalSettings::parallelJobsFixedCount(); + + *this << QStringLiteral("-j%1").arg(threadsCount); + } + + *this << QStringLiteral("-f"); + *this << buildMakefile(params); +} + +Job::~Job() +{ + doKill(); +} + +inline QString spaceEscapedString(const QString& s) +{ + return QString(s).replace(QLatin1Char(' '), QLatin1String("\\ ")); +} + +QString Job::buildMakefile(const JobParameters& params) +{ + const auto makefilePath = QStringLiteral("%1/kdevclazy.makefile").arg(params.projectBuildPath()); + + QFile makefile(makefilePath); + makefile.open(QIODevice::WriteOnly); + + QTextStream scriptStream(&makefile); + + // Since GNU make (and maybe other make versions) fails on files/paths with whitespaces + // we should perform space-escaping procedure for all potential strings. + + scriptStream << QStringLiteral("SOURCES ="); + for (const QString& source : params.sources()) { + scriptStream << QStringLiteral(" %1").arg(spaceEscapedString(source)); + } + scriptStream << QLatin1Char('\n'); + + scriptStream << QStringLiteral("COMMAND ="); + if (!GlobalSettings::verboseOutput()) { + scriptStream << QLatin1Char('@'); + } + const auto commandLine = params.commandLine(); + for (const QString& commandPart : commandLine) { + scriptStream << QStringLiteral(" %1").arg(spaceEscapedString(commandPart)); + } + scriptStream << QLatin1Char('\n'); + + scriptStream << QStringLiteral(".PHONY: all $(SOURCES)\n"); + scriptStream << QStringLiteral("all: $(SOURCES)\n"); + scriptStream << QStringLiteral("$(SOURCES):\n"); + + scriptStream << QStringLiteral("\t@echo 'Clazy check started for $@'\n"); + // Wrap filename ($@) with quotas to handle "whitespaced" file names. + scriptStream << QStringLiteral("\t$(COMMAND) '$@'\n"); + scriptStream << QStringLiteral("\t@echo 'Clazy check finished for $@'\n"); + + makefile.close(); + + m_totalCount = params.sources().size(); + + return makefilePath; +} + +void Job::postProcessStdout(const QStringList& lines) +{ + static const auto startedRegex = QRegularExpression("Clazy check started for (.+)$"); + static const auto finishedRegex = QRegularExpression("Clazy check finished for (.+)$"); + + for (const QString & line : lines) { + auto match = startedRegex.match(line); + if (match.hasMatch()) { + emit infoMessage(this, match.captured(1)); + continue; + } + + match = finishedRegex.match(line); + if (match.hasMatch()) { + setPercent(++m_finishedCount/(double)m_totalCount * 100); + continue; + } + } + + m_standardOutput << lines; + + if (status() == KDevelop::OutputExecuteJob::JobStatus::JobRunning) { + OutputExecuteJob::postProcessStdout(lines); + } +} + +void Job::postProcessStderr(const QStringList& lines) +{ + static const auto errorRegex = QRegularExpression( + QStringLiteral("(.+):(\\d+):(\\d+):\\s+warning:\\s+(.+)\\s+\\[-Wclazy-(.+)\\]$")); + + QVector problems; + + for (const QString & line : lines) { + auto match = errorRegex.match(line); + if (match.hasMatch()) { + auto check = m_db ? m_db->checks().value(match.captured(5), nullptr) : nullptr; + + const QString levelName = check ? check->level->displayName : i18n("Unknown Level"); + KDevelop::IProblem::Ptr problem(new KDevelop::DetectedProblem(levelName)); + + problem->setSeverity(KDevelop::IProblem::Warning); + problem->setDescription(match.captured(4)); + if (check) { + problem->setExplanation(check->description); + } + + const auto document = QFileInfo(match.captured(1)).canonicalFilePath(); + const int line = match.captured(2).toInt() - 1; + const int column = match.captured(3).toInt() - 1; + + // TODO add KDevelop::IProblem::FinalLocationMode::ToEnd type ? + KTextEditor::Range range(line, column, line, 1000); + KDevelop::DocumentRange documentRange(KDevelop::IndexedString(document), range); + problem->setFinalLocation(documentRange); + problem->setFinalLocationMode(KDevelop::IProblem::Range); + + problems.append(problem); + } + } + m_stderrOutput << lines; + + if (problems.size()) { + emit problemsDetected(problems); + } + + if (status() == KDevelop::OutputExecuteJob::JobStatus::JobRunning) { + OutputExecuteJob::postProcessStderr(lines); + } +} + +void Job::start() +{ + m_standardOutput.clear(); + m_stderrOutput.clear(); + + qCDebug(KDEV_CLAZY) << "executing:" << commandLine().join(QLatin1Char(' ')); + + m_timer->restart(); + setPercent(0); + m_finishedCount = 0; + + OutputExecuteJob::start(); +} + +void Job::childProcessError(QProcess::ProcessError e) +{ + QString message; + + switch (e) { + case QProcess::FailedToStart: + message = i18n("Failed to start Clazy analysis process."); + break; + + case QProcess::Crashed: + if (status() != KDevelop::OutputExecuteJob::JobStatus::JobCanceled) { + message = i18n("Clazy analysis process crashed."); + } + break; + + case QProcess::Timedout: + message = i18n("Clazy analysis process timed out."); + break; + + case QProcess::WriteError: + message = i18n("Write to Clazy analysis process failed."); + break; + + case QProcess::ReadError: + message = i18n("Read from Clazy analysis process failed."); + break; + + case QProcess::UnknownError: + // errors will be displayed in the output view ? + // don't notify the user + break; + } + + if (!message.isEmpty()) { + KMessageBox::error(qApp->activeWindow(), message, i18n("Clazy Error")); + } + + KDevelop::OutputExecuteJob::childProcessError(e); +} + +void Job::childProcessExited(int exitCode, QProcess::ExitStatus exitStatus) +{ + qCDebug(KDEV_CLAZY) << "Process Finished, exitCode" << exitCode << "process exit status" << exitStatus; + + setPercent(100); + postProcessStdout({QStringLiteral("Elapsed time: %1 s.").arg(m_timer->elapsed()/1000.0)}); + + if (exitCode != 0) { + qCDebug(KDEV_CLAZY) << "clazy failed"; + qCDebug(KDEV_CLAZY) << "stdout output: "; + qCDebug(KDEV_CLAZY) << m_standardOutput.join(QLatin1Char('\n')); + qCDebug(KDEV_CLAZY) << "stderr output: "; + qCDebug(KDEV_CLAZY) << m_stderrOutput.join(QLatin1Char('\n')); + } + + KDevelop::OutputExecuteJob::childProcessExited(exitCode, exitStatus); +} + +} diff --git a/plugins/clazy/job.h b/plugins/clazy/job.h new file mode 100644 index 0000000000..8dad9079cf --- /dev/null +++ b/plugins/clazy/job.h @@ -0,0 +1,74 @@ +/* This file is part of KDevelop + + Copyright 2018 Anton Anikin + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#ifndef KDEVCLAZY_JOB_H +#define KDEVCLAZY_JOB_H + +#include +#include + +class QElapsedTimer; + +namespace Clazy +{ + +class ChecksDB; +class JobParameters; + +class Job : public KDevelop::OutputExecuteJob +{ + Q_OBJECT + +public: + Job(const JobParameters& params, QSharedPointer db); + ~Job() override; + + void start() override; + +Q_SIGNALS: + void problemsDetected(const QVector& problems); + +protected Q_SLOTS: + void postProcessStdout(const QStringList& lines) override; + void postProcessStderr(const QStringList& lines) override; + + void childProcessExited(int exitCode, QProcess::ExitStatus exitStatus) override; + void childProcessError(QProcess::ProcessError processError) override; + +protected: + /// Empty constructor which creates invalid Job instance. Used only for testing + Job(); + + int m_totalCount = 0; + int m_finishedCount = 0; + +private: + QString buildMakefile(const JobParameters& params); + +private: + QSharedPointer m_db; + QScopedPointer m_timer; + + QStringList m_standardOutput; + QStringList m_stderrOutput; +}; + +} +#endif diff --git a/plugins/clazy/jobparameters.cpp b/plugins/clazy/jobparameters.cpp new file mode 100644 index 0000000000..d7eb2c963a --- /dev/null +++ b/plugins/clazy/jobparameters.cpp @@ -0,0 +1,366 @@ +/* This file is part of KDevelop + + Copyright 2018 Anton Anikin + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#include "jobparameters.h" + +#include "debug.h" +#include "globalsettings.h" +#include "projectsettings.h" +#include "utils.h" + +#include +#include +#include +#include + +#include +#include + +#include +#include + +namespace Clazy +{ + +JobGlobalParameters::JobGlobalParameters() + : JobGlobalParameters(GlobalSettings::executablePath(), GlobalSettings::docsPath()) +{ +} + +JobGlobalParameters::JobGlobalParameters(const QUrl& executablePath, const QUrl& docsPath) +{ + m_executablePath = executablePath.toLocalFile(); + m_docsPath = docsPath.toLocalFile(); + + QFileInfo info; + + if (m_executablePath.isEmpty()) { + if (defaultExecutablePath().toLocalFile().isEmpty()) { + m_error = i18n( + "clazy-standalone path cannot be detected. " + "Set the path manually if Clazy is already installed."); + } else { + m_error = i18n("clazy-standalone path is empty."); + } + return; + } + + info.setFile(m_executablePath); + + if (!info.exists()) { + m_error = i18n("clazy-standalone path '%1' does not exists.", m_executablePath); + return; + } + + if (!info.isFile() || !info.isExecutable()) { + m_error = i18n("clazy-standalone path '%1' is not an executable.", m_executablePath); + return; + } + + // ============================================================================================= + + if (m_docsPath.isEmpty()) { + if (defaultDocsPath().toLocalFile().isEmpty()) { + m_error = i18n( + "Clazy documentation path cannot be detected. " + "Set the path manually if Clazy is already installed."); + } else { + m_error = i18n("Clazy documentation path is empty."); + } + return; + } + + info.setFile(m_docsPath); + + if (!info.exists()) { + m_error = i18n("Clazy documentation path '%1' does not exists.", m_docsPath); + return; + } + + if (!info.isDir()) { + m_error = i18n("Clazy documentation path '%1' is not a directory.", m_docsPath); + return; + } + + m_error.clear(); +} + +QUrl JobGlobalParameters::defaultExecutablePath() +{ + return QUrl::fromLocalFile(QStandardPaths::findExecutable(QStringLiteral("clazy-standalone"))); +} + +QUrl JobGlobalParameters::defaultDocsPath() +{ + const auto docsPath = QStandardPaths::locate( + QStandardPaths::GenericDataLocation, + QStringLiteral("clazy/doc"), + QStandardPaths::LocateDirectory); + + if (docsPath.isEmpty()) { + return {}; + } + + return QUrl::fromLocalFile(QDir(docsPath).canonicalPath()); +} + +bool JobGlobalParameters::isValid() const +{ + return m_error.isEmpty(); +} + +QString JobGlobalParameters::error() const +{ + return m_error; +} + +JobParameters::JobParameters(KDevelop::IProject* project) + : JobParameters(project, QString()) +{ +} + +JobParameters::JobParameters(KDevelop::IProject* project, const QString& checkPath) + : m_checkPath(checkPath) +{ + Q_ASSERT(project); + + auto projectRootPath = project->path().toLocalFile(); + + auto buildPath = project->buildSystemManager()->buildDirectory(project->projectItem()); + m_projectBuildPath = buildPath.toLocalFile(); + + buildPath.addPath(QStringLiteral("compile_commands.json")); + + auto commandsFilePath = buildPath.toLocalFile(); + if (!QFile::exists(commandsFilePath)) { + m_error = i18n("Compile commands file '%1' does not exist.", commandsFilePath); + return; + } + + if (!m_checkPath.isEmpty()) { + const auto allFiles = compileCommandsFiles(commandsFilePath, m_error); + if (!m_error.isEmpty()) { + return; + } + + if (m_checkPath == projectRootPath) { + m_sources = allFiles; + } else { + const bool checkPathIsFile = QFileInfo(m_checkPath).isFile(); + for (auto& file : allFiles) { + if (checkPathIsFile) { + if (file == m_checkPath) { + m_sources.clear(); + m_sources += m_checkPath; + break; + } + } else if (file.startsWith(m_checkPath)) { + m_sources += file; + } + } + } + } + + ProjectSettings projectSettings; + projectSettings.setSharedConfig(project->projectConfiguration()); + projectSettings.load(); + + { + // blocks changed() signal from setters + QSignalBlocker blocker(this); + + setChecks(projectSettings.checks()); + + setOnlyQt(projectSettings.onlyQt()); + setQtDeveloper(projectSettings.qtDeveloper()); + setQt4Compat(projectSettings.qt4Compat()); + setVisitImplicitCode(projectSettings.visitImplicitCode()); + + setIgnoreIncludedFiles(projectSettings.ignoreIncludedFiles()); + setHeaderFilter(projectSettings.headerFilter()); + + setEnableAllFixits(projectSettings.enableAllFixits()); + setNoInplaceFixits(projectSettings.noInplaceFixits()); + + setExtraAppend(projectSettings.extraAppend()); + setExtraPrepend(projectSettings.extraPrepend()); + setExtraClazy(projectSettings.extraClazy()); + } + + if (m_sources.isEmpty()) { + m_error = i18n("Nothing to check: compile commands file '%1' contains no matching items.", commandsFilePath); + } +} + +QString JobParameters::defaultChecks() +{ + return QStringLiteral("level1"); +} + +QString JobParameters::checkPath() const +{ + return m_checkPath; +} + +const QStringList& JobParameters::sources() const +{ + return m_sources; +} + +QString JobParameters::projectBuildPath() const +{ + return m_projectBuildPath; +} + +QStringList JobParameters::commandLine() const +{ + QStringList arguments; + + arguments << m_executablePath; + + if (!m_checks.isEmpty()) { + arguments << QStringLiteral("-checks=%1").arg(m_checks); + } + + if (m_onlyQt) { + arguments << QStringLiteral("-only-qt"); + } + + if (m_qtDeveloper) { + arguments << QStringLiteral("-qt-developer"); + } + + if (m_qt4Compat) { + arguments << QStringLiteral("-qt4-compat"); + } + + if (m_visitImplicitCode) { + arguments << QStringLiteral("-visit-implicit-code"); + } + + if (m_ignoreIncludedFiles) { + arguments << QStringLiteral("-ignore-included-files"); + } + + if (!m_headerFilter.isEmpty()) { + arguments << QStringLiteral("-header-filter=%1").arg(m_headerFilter); + } + + if (m_enableAllFixits) { + arguments << QStringLiteral("-enable-all-fixits"); + } + + if (m_noInplaceFixits) { + arguments << QStringLiteral("-no-inplace-fixits"); + } + + if (!m_extraAppend.isEmpty()) { + arguments << QStringLiteral("-extra-arg=%1").arg(m_extraAppend); + } + + if (!m_extraPrepend.isEmpty()) { + arguments << QStringLiteral("-extra-arg-before=%1").arg(m_extraPrepend); + } + + if (!m_extraClazy.isEmpty()) { + arguments << KShell::splitArgs(m_extraClazy); + } + + arguments << QStringLiteral("-p"); + arguments << m_projectBuildPath; + + return arguments; +} + + +template +void JobParameters::setValue(T& currentValue, const T& newValue) +{ + if (currentValue != newValue) { + currentValue = newValue; + emit changed(); + } +} + +void JobParameters::setChecks(const QString& checks) +{ + if (checks.isEmpty()) { + setValue(m_checks, defaultChecks()); + } else { + setValue(m_checks, checks); + } +} + +void JobParameters::setOnlyQt(bool onlyQt) +{ + setValue(m_onlyQt, onlyQt); +} + +void JobParameters::setQtDeveloper(bool qtDeveloper) +{ + setValue(m_qtDeveloper, qtDeveloper); +} + +void JobParameters::setQt4Compat(bool qt4Compat) +{ + setValue(m_qt4Compat, qt4Compat); +} + +void JobParameters::setVisitImplicitCode(bool visitImplicitCode) +{ + setValue(m_visitImplicitCode, visitImplicitCode); +} + +void JobParameters::setIgnoreIncludedFiles(bool ignoreIncludedFiles) +{ + setValue(m_ignoreIncludedFiles, ignoreIncludedFiles); +} + +void JobParameters::setHeaderFilter(const QString& headerFilter) +{ + setValue(m_headerFilter, headerFilter); +} + +void JobParameters::setEnableAllFixits(bool enableAllFixits) +{ + setValue(m_enableAllFixits, enableAllFixits); +} + +void JobParameters::setNoInplaceFixits(bool noInplaceFixits) +{ + setValue(m_noInplaceFixits, noInplaceFixits); +} + +void JobParameters::setExtraAppend(const QString& extraAppend) +{ + setValue(m_extraAppend, extraAppend); +} + +void JobParameters::setExtraPrepend(const QString& extraPrepend) +{ + setValue(m_extraPrepend, extraPrepend); +} + +void JobParameters::setExtraClazy(const QString& extraClazy) +{ + setValue(m_extraClazy, extraClazy); +} + +} diff --git a/plugins/clazy/jobparameters.h b/plugins/clazy/jobparameters.h new file mode 100644 index 0000000000..f62c67d552 --- /dev/null +++ b/plugins/clazy/jobparameters.h @@ -0,0 +1,123 @@ +/* This file is part of KDevelop + + Copyright 2018 Anton Anikin + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#ifndef KDEVCLAZY_JOB_PARAMETERS_H +#define KDEVCLAZY_JOB_PARAMETERS_H + +#include +#include +#include + +namespace KDevelop { class IProject; } + +namespace Clazy +{ + +class JobGlobalParameters : public QObject +{ + Q_OBJECT + +public: + JobGlobalParameters(const QUrl& executablePath, const QUrl& docsPath); + ~JobGlobalParameters() override = default; + + static QUrl defaultExecutablePath(); + static QUrl defaultDocsPath(); + + bool isValid() const; + QString error() const; + +Q_SIGNALS: + void changed(); + +protected: + JobGlobalParameters(); + + QString m_executablePath; + QString m_docsPath; + + QString m_error; +}; + +class JobParameters : public JobGlobalParameters +{ + Q_OBJECT + +public: + explicit JobParameters(KDevelop::IProject* project); + JobParameters(KDevelop::IProject* project, const QString& checkPath); + ~JobParameters() override = default; + + static QString defaultChecks(); + + QString checkPath() const; + const QStringList& sources() const; + + QString projectBuildPath() const; + + QStringList commandLine() const; + + void setChecks(const QString& checks); + + void setOnlyQt(bool onlyQt); + void setQtDeveloper(bool qtDeveloper); + void setQt4Compat(bool qt4Compat); + void setVisitImplicitCode(bool visitImplicitCode); + + void setIgnoreIncludedFiles(bool ignoreIncludedFiles); + void setHeaderFilter(const QString& headerFilter); + + void setEnableAllFixits(bool enableAllFixits); + void setNoInplaceFixits(bool noInplaceFixits); + + void setExtraAppend(const QString& extraAppend); + void setExtraPrepend(const QString& extraPrepend); + void setExtraClazy(const QString& extraClazy); + +private: + template + void setValue(T& currentValue, const T& newValue); + +private: + QString m_checkPath; + QStringList m_sources; + + QString m_projectBuildPath; + + QString m_checks; + + bool m_onlyQt; + bool m_qtDeveloper; + bool m_qt4Compat; + bool m_visitImplicitCode; + + bool m_ignoreIncludedFiles; + QString m_headerFilter; + + bool m_enableAllFixits; + bool m_noInplaceFixits; + + QString m_extraAppend; + QString m_extraPrepend; + QString m_extraClazy; +}; + +} +#endif diff --git a/plugins/clazy/kdevclazy.json b/plugins/clazy/kdevclazy.json new file mode 100644 index 0000000000..4b4abad262 --- /dev/null +++ b/plugins/clazy/kdevclazy.json @@ -0,0 +1,23 @@ +{ + "KPlugin": { + "Authors": [ + { + "Name": "Anton Anikin" + } + ], + "Category": "Analyzers", + "Description": "This plugin integrates Clazy to KDevelop", + "Icon": "clazy", + "Id": "kdevclazy", + "License": "GPL", + "Name": "Clazy Support", + "ServiceTypes": [ + "KDevelop/Plugin" + ] + }, + "X-KDevelop-Category": "Global", + "X-KDevelop-IRequired": [ + "org.kdevelop.IExecutePlugin" + ], + "X-KDevelop-Mode": "GUI" +} diff --git a/plugins/clazy/kdevclazy.qrc b/plugins/clazy/kdevclazy.qrc new file mode 100644 index 0000000000..c524e5b3f6 --- /dev/null +++ b/plugins/clazy/kdevclazy.qrc @@ -0,0 +1,6 @@ + + + + kdevclazy.rc + + diff --git a/plugins/clazy/kdevclazy.rc b/plugins/clazy/kdevclazy.rc new file mode 100644 index 0000000000..61089fa864 --- /dev/null +++ b/plugins/clazy/kdevclazy.rc @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/plugins/clazy/plugin.cpp b/plugins/clazy/plugin.cpp new file mode 100644 index 0000000000..e52d5ab293 --- /dev/null +++ b/plugins/clazy/plugin.cpp @@ -0,0 +1,291 @@ +/* This file is part of KDevelop + + Copyright 2018 Anton Anikin + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#include "plugin.h" + +#include "checksdb.h" +#include "config/globalconfigpage.h" +#include "config/projectconfigpage.h" +#include "debug.h" +#include "globalsettings.h" +#include "problemmodel.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include + +K_PLUGIN_FACTORY_WITH_JSON(ClazyFactory, "kdevclazy.json", registerPlugin();) + +namespace Clazy +{ + +Plugin::Plugin(QObject* parent, const QVariantList&) + : IPlugin("kdevclazy", parent) + , m_job(nullptr) + , m_project(nullptr) + , m_model(new ProblemModel(this)) + , m_db(nullptr) +{ + setXMLFile("kdevclazy.rc"); + + const QIcon clazyIcon = QIcon::fromTheme(QStringLiteral("clazy")); + + auto runFileAnalysis = [this]() { runClazy(false); }; + auto runProjectAnalysis = [this]() { runClazy(true); }; + + m_menuActionFile = new QAction(clazyIcon, i18n("Analyze Current File with Clazy"), this); + connect(m_menuActionFile, &QAction::triggered, this, runFileAnalysis); + actionCollection()->addAction("clazy_file", m_menuActionFile); + + m_contextActionFile = new QAction(clazyIcon, i18n("Clazy"), this); + connect(m_contextActionFile, &QAction::triggered, this, runFileAnalysis); + + m_menuActionProject = new QAction(clazyIcon, i18n("Analyze Current Project with Clazy"), this); + connect(m_menuActionProject, &QAction::triggered, this, runProjectAnalysis); + actionCollection()->addAction("clazy_project", m_menuActionProject); + + m_contextActionProject = new QAction(clazyIcon, i18n("Clazy"), this); + connect(m_contextActionProject, &QAction::triggered, this, runProjectAnalysis); + + m_contextActionProjectItem = new QAction(clazyIcon, i18n("Clazy"), this); + + connect(core()->documentController(), &KDevelop::IDocumentController::documentClosed, + this, &Plugin::updateActions); + + connect(core()->documentController(), &KDevelop::IDocumentController::documentActivated, + this, &Plugin::updateActions); + + connect(core()->projectController(), &KDevelop::IProjectController::projectOpened, + this, &Plugin::updateActions); + + connect(core()->projectController(), &KDevelop::IProjectController::projectClosed, + this, &Plugin::projectClosed); + + updateActions(); +} + +Plugin::~Plugin() +{ + killClazy(); +} + +bool Plugin::isRunning() const +{ + return (m_job != nullptr); +} + +void Plugin::killClazy() +{ + if (m_job) { + m_job->kill(KJob::EmitResult); + } +} + +void Plugin::raiseProblemsView() +{ + m_model->show(); +} + +void Plugin::raiseOutputView() +{ + core()->uiController()->findToolView( + i18ndc("kdevstandardoutputview", "@title:window", "Test"), + nullptr, + KDevelop::IUiController::FindFlags::Raise); +} + +void Plugin::updateActions() +{ + m_project = nullptr; + + m_menuActionFile->setEnabled(false); + m_menuActionProject->setEnabled(false); + + if (isRunning()) { + return; + } + + auto activeDocument = core()->documentController()->activeDocument(); + if (!activeDocument) { + return; + } + + m_project = core()->projectController()->findProjectForUrl(activeDocument->url()); + if (!m_project) { + return; + } + + m_menuActionFile->setEnabled(true); + m_menuActionProject->setEnabled(true); +} + +void Plugin::projectClosed(KDevelop::IProject* project) +{ + if (project != m_model->project()) { + return; + } + + killClazy(); + m_model->reset(); +} + +void Plugin::runClazy(bool checkProject) +{ + auto doc = core()->documentController()->activeDocument(); + Q_ASSERT(doc); + + if (checkProject) { + runClazy(m_project, m_project->path().toUrl().toLocalFile()); + } else { + runClazy(m_project, doc->url().toLocalFile()); + } +} + +void Plugin::runClazy(KDevelop::IProject* project, const QString& path) +{ + JobParameters params(project, path); + if (!params.isValid()) { + const QString errorMessage = i18n("Unable to start Clazy check for '%1':\n\n%2", path, params.error()); + KMessageBox::error(qApp->activeWindow(), errorMessage, i18n("Clazy Error")); + return; + } + + m_model->reset(project, path); + + if (m_db.isNull()) { + reloadDB(); + } + + m_job = new Job(params, m_db); + connect(m_job, &Job::problemsDetected, m_model, &ProblemModel::addProblems); + connect(m_job, &Job::finished, this, &Plugin::result); + + core()->uiController()->registerStatus(new KDevelop::JobStatus(m_job, "clazy")); + core()->runController()->registerJob(m_job); + + if (GlobalSettings::hideOutputView()) { + raiseProblemsView(); + } else { + raiseOutputView(); + } + + updateActions(); +} + +void Plugin::result(KJob*) +{ + if (!core()->projectController()->projects().contains(m_model->project())) { + m_model->reset(); + } else { + m_model->setProblems(); + + if (m_job->status() == KDevelop::OutputExecuteJob::JobStatus::JobSucceeded || + m_job->status() == KDevelop::OutputExecuteJob::JobStatus::JobCanceled) { + + raiseProblemsView(); + } else { + raiseOutputView(); + } + } + + m_job = nullptr; // job automatically deletes itself later + + updateActions(); +} + +KDevelop::ContextMenuExtension Plugin::contextMenuExtension(KDevelop::Context* context, QWidget* parent) +{ + Q_UNUSED(parent); + KDevelop::ContextMenuExtension extension; + + if (context->hasType(KDevelop::Context::EditorContext) && m_project && !isRunning()) { + extension.addAction(KDevelop::ContextMenuExtension::AnalyzeFileGroup, m_contextActionFile); + extension.addAction(KDevelop::ContextMenuExtension::AnalyzeProjectGroup, m_contextActionProject); + } + + if (context->hasType(KDevelop::Context::ProjectItemContext) && !isRunning()) { + auto pContext = dynamic_cast(context); + if (pContext->items().size() != 1) { + return extension; + } + + auto item = pContext->items().constFirst(); + switch (item->type()) { + case KDevelop::ProjectBaseItem::File: + case KDevelop::ProjectBaseItem::Folder: + case KDevelop::ProjectBaseItem::BuildFolder: + break; + + default: + return extension; + } + + m_contextActionProjectItem->disconnect(); + connect(m_contextActionProjectItem, &QAction::triggered, this, [this, item](){ + runClazy(item->project(), item->path().toLocalFile()); + }); + + extension.addAction(KDevelop::ContextMenuExtension::AnalyzeProjectGroup, m_contextActionProjectItem); + } + + return extension; +} + +KDevelop::ConfigPage* Plugin::perProjectConfigPage(int number, const KDevelop::ProjectConfigOptions& options, QWidget* parent) +{ + if (m_db.isNull()) { + reloadDB(); + } + + return number ? nullptr : new ProjectConfigPage(this, options.project, parent); +} + +KDevelop::ConfigPage* Plugin::configPage(int number, QWidget* parent) +{ + return number ? nullptr : new GlobalConfigPage(this, parent); +} + +QSharedPointer Plugin::checksDB() const +{ + return m_db; +} + +void Plugin::reloadDB() +{ + m_db.reset(new ChecksDB(GlobalSettings::docsPath())); + connect(GlobalSettings::self(), &GlobalSettings::docsPathChanged, this, &Plugin::reloadDB); +} + +} + +#include "plugin.moc" diff --git a/plugins/clazy/plugin.h b/plugins/clazy/plugin.h new file mode 100644 index 0000000000..68dc82c4e7 --- /dev/null +++ b/plugins/clazy/plugin.h @@ -0,0 +1,91 @@ +/* This file is part of KDevelop + + Copyright 2018 Anton Anikin + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#ifndef KDEVCLAZY_PLUGIN_H +#define KDEVCLAZY_PLUGIN_H + +#include "job.h" + +#include + +class KJob; + +namespace KDevelop { class IProject; } + +namespace Clazy +{ + +class ChecksDB; +class ProblemModel; + +class Plugin : public KDevelop::IPlugin +{ + Q_OBJECT + +public: + Plugin(QObject* parent, const QVariantList& = QVariantList()); + ~Plugin() override; + + int configPages() const override { return 1; } + KDevelop::ConfigPage* configPage(int number, QWidget* parent) override; + + int perProjectConfigPages() const override { return 1; } + KDevelop::ConfigPage* perProjectConfigPage(int number, const KDevelop::ProjectConfigOptions& options, QWidget* parent) override; + + KDevelop::ContextMenuExtension contextMenuExtension(KDevelop::Context* context, QWidget* parent) override; + + void runClazy(KDevelop::IProject* project, const QString& path); + bool isRunning() const; + + QSharedPointer checksDB() const; + +private: + void killClazy(); + + void raiseProblemsView(); + void raiseOutputView(); + + void updateActions(); + void projectClosed(KDevelop::IProject* project); + + void runClazy(bool checkProject); + + void result(KJob* job); + + void reloadDB(); + +private: + Job* m_job; + + KDevelop::IProject* m_project; + ProblemModel* m_model; + + QAction* m_menuActionFile; + QAction* m_menuActionProject; + QAction* m_contextActionFile; + QAction* m_contextActionProject; + QAction* m_contextActionProjectItem; + + QSharedPointer m_db; +}; + +} + +#endif diff --git a/plugins/clazy/problemmodel.cpp b/plugins/clazy/problemmodel.cpp new file mode 100644 index 0000000000..c55b847c3c --- /dev/null +++ b/plugins/clazy/problemmodel.cpp @@ -0,0 +1,168 @@ +/* This file is part of KDevelop + + Copyright 2018 Anton Anikin + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#include "problemmodel.h" + +#include "plugin.h" +#include "utils.h" + +#include +#include +#include + +#include + +namespace Clazy +{ + +inline KDevelop::ProblemModelSet* problemModelSet() +{ + return KDevelop::ICore::self()->languageController()->problemModelSet(); +} + +inline QString problemModelId() +{ + return QStringLiteral("clazy"); +} + +ProblemModel::ProblemModel(Plugin* plugin) + : KDevelop::ProblemModel(plugin) + , m_plugin(plugin) + , m_project(nullptr) + , m_pathLocation(KDevelop::DocumentRange::invalid()) +{ + setFeatures(CanDoFullUpdate | + ScopeFilter | + SeverityFilter | + Grouping | + CanByPassScopeFilter| + ShowSource); + + reset(); + + problemModelSet()->addModel(problemModelId(), i18n("Clazy"), this); +} + +ProblemModel::~ProblemModel() +{ + problemModelSet()->removeModel(problemModelId()); +} + +KDevelop::IProject* ProblemModel::project() const +{ + return m_project; +} + +void ProblemModel::setMessage(const QString& message) +{ + setPlaceholderText(message, m_pathLocation, i18n("Clazy")); +} + +// The code is adapted version of cppcheck::ProblemModel::problemExists() +// TODO Add into KDevelop::ProblemModel class ? +bool ProblemModel::problemExists(KDevelop::IProblem::Ptr newProblem) +{ + for (const auto& problem : qAsConst(m_problems)) { + if (newProblem->source() == problem->source() && + newProblem->sourceString() == problem->sourceString() && + newProblem->severity() == problem->severity() && + newProblem->finalLocation() == problem->finalLocation() && + newProblem->description() == problem->description() && + newProblem->explanation() == problem->explanation()) + return true; + } + + return false; +} + +// The code is adapted version of cppcheck::ProblemModel::addProblems() +// TODO Add into KDevelop::ProblemModel class ? +void ProblemModel::addProblems(const QVector& problems) +{ + static int maxLength = 0; + + if (m_problems.isEmpty()) { + maxLength = 0; + } + + for (const auto& problem : problems) { + if (problemExists(problem)) { + continue; + } + + m_problems.append(problem); + addProblem(problem); + + // This performs adjusting of columns width in the ProblemsView + if (maxLength < problem->description().length()) { + maxLength = problem->description().length(); + setProblems(m_problems); + } + } +} + +void ProblemModel::setProblems() +{ + if (m_problems.isEmpty()) { + setMessage(i18n("Analysis completed, no problems detected.")); + } else { + setProblems(m_problems); + } +} + +void ProblemModel::reset() +{ + reset(nullptr, QString()); +} + +void ProblemModel::reset(KDevelop::IProject* project, const QString& path) +{ + m_project = project; + + m_path = path; + m_pathLocation.document = KDevelop::IndexedString(path); + + clearProblems(); + m_problems.clear(); + + QString tooltip; + if (m_project) { + setMessage(i18n("Analysis started...")); + tooltip = i18nc("@info:tooltip %1 is the path of the file", "Re-run last Clazy analysis (%1)", prettyPathName(m_path)); + } else { + tooltip = i18nc("@info:tooltip", "Re-run last Clazy analysis"); + } + + setFullUpdateTooltip(tooltip); +} + +void ProblemModel::show() +{ + problemModelSet()->showModel(problemModelId()); +} + +void ProblemModel::forceFullUpdate() +{ + if (m_project && !m_plugin->isRunning()) { + m_plugin->runClazy(m_project, m_path); + } +} + +} diff --git a/plugins/clazy/problemmodel.h b/plugins/clazy/problemmodel.h new file mode 100644 index 0000000000..77278145da --- /dev/null +++ b/plugins/clazy/problemmodel.h @@ -0,0 +1,72 @@ +/* This file is part of KDevelop + + Copyright 2018 Anton Anikin + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#ifndef KDEVCLAZY_PROBLEM_MODEL_H +#define KDEVCLAZY_PROBLEM_MODEL_H + +#include + +namespace KDevelop { class IProject; } + +namespace Clazy +{ + +class Plugin; + +class ProblemModel : public KDevelop::ProblemModel +{ + Q_OBJECT + +public: + explicit ProblemModel(Plugin* plugin); + ~ProblemModel() override; + + KDevelop::IProject* project() const; + + void addProblems(const QVector& problems); + + void setProblems(); + using KDevelop::ProblemModel::setProblems; + + void reset(); + void reset(KDevelop::IProject* project, const QString& path); + + void show(); + + void forceFullUpdate() override; + +private: + void setMessage(const QString& message); + bool problemExists(KDevelop::IProblem::Ptr newProblem); + +private: + Plugin* m_plugin; + + KDevelop::IProject* m_project; + + QString m_path; + KDevelop::DocumentRange m_pathLocation; + + QVector m_problems; +}; + +} + +#endif diff --git a/plugins/clazy/tests/CMakeLists.txt b/plugins/clazy/tests/CMakeLists.txt new file mode 100644 index 0000000000..d596fc3fbe --- /dev/null +++ b/plugins/clazy/tests/CMakeLists.txt @@ -0,0 +1,6 @@ +ecm_add_test( + test_clazyjob.cpp + + TEST_NAME test_clazyjob + LINK_LIBRARIES kdevclazy_core Qt5::Test KDev::Tests +) diff --git a/plugins/clazy/tests/test_clazyjob.cpp b/plugins/clazy/tests/test_clazyjob.cpp new file mode 100644 index 0000000000..8c200a7871 --- /dev/null +++ b/plugins/clazy/tests/test_clazyjob.cpp @@ -0,0 +1,213 @@ +/* This file is part of KDevelop + + Copyright 2018 Anton Anikin + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#include "test_clazyjob.h" + +#include "job.h" + +#include +#include +#include + +#include + +using namespace KDevelop; +using namespace Clazy; + +class JobTester : public Job +{ + Q_OBJECT + +public: + JobTester() + { + connect(this, &JobTester::problemsDetected, + this, [this](const QVector& problems) { + m_problems += problems; + }); + + connect(this, &JobTester::infoMessage, + this, [this](KJob*, const QString& name) { + m_started += name; + }); + } + + ~JobTester() override = default; + + using Job::postProcessStdout; + using Job::postProcessStderr; + + const QVector& problems() const + { + return m_problems; + } + + const QList& started() const + { + return m_started; + } + + void setTotalCount(int totalCount) + { + m_totalCount = totalCount; + } + + int finishedCount() const + { + return m_finishedCount; + } + +private: + QVector m_problems; + QList m_started; +}; + +void TestClazyJob::initTestCase() +{ + AutoTestShell::init({"kdevclazy"}); + TestCore::initialize(Core::NoUi); +} + +void TestClazyJob::cleanupTestCase() +{ + TestCore::shutdown(); +} + +void TestClazyJob::testJob() +{ + JobTester jobTester; + + // test progress parsing ======================================================================= + + static const QStringList stdoutOutput1 = { + QStringLiteral("Clazy check started for source2.cpp"), + QStringLiteral("Clazy check started for source1.cpp") + }; + + static const QStringList stdoutOutput2 = { + QStringLiteral("Clazy check finished for source2.cpp"), + QStringLiteral("Clazy check started for source3.cpp"), + QStringLiteral("Clazy check started for source4.cpp") + }; + + static const QStringList stdoutOutput3 = { + QStringLiteral("Clazy check finished for source1.cpp"), + QStringLiteral("Clazy check finished for source4.cpp") + }; + + static const QStringList stdoutOutput4 = { + QStringLiteral("Clazy check finished for source3.cpp"), + }; + + jobTester.setTotalCount(4); + + jobTester.postProcessStdout(stdoutOutput1); + QCOMPARE(jobTester.started().size(), 2); + QCOMPARE(jobTester.started().at(0), QStringLiteral("source2.cpp")); + QCOMPARE(jobTester.started().at(1), QStringLiteral("source1.cpp")); + QCOMPARE(jobTester.finishedCount(), 0); + QCOMPARE(jobTester.percent(), 0); + + jobTester.postProcessStdout(stdoutOutput2); + QCOMPARE(jobTester.started().size(), 4); + QCOMPARE(jobTester.started().at(2), QStringLiteral("source3.cpp")); + QCOMPARE(jobTester.started().at(3), QStringLiteral("source4.cpp")); + QCOMPARE(jobTester.finishedCount(), 1); + QCOMPARE(jobTester.percent(), 25); + + jobTester.postProcessStdout(stdoutOutput3); + QCOMPARE(jobTester.started().size(), 4); + QCOMPARE(jobTester.finishedCount(), 3); + QCOMPARE(jobTester.percent(), 75); + + jobTester.postProcessStdout(stdoutOutput4); + QCOMPARE(jobTester.started().size(), 4); + QCOMPARE(jobTester.finishedCount(), 4); + QCOMPARE(jobTester.percent(), 100); + + QCOMPARE(jobTester.started().at(0), QStringLiteral("source2.cpp")); + QCOMPARE(jobTester.started().at(1), QStringLiteral("source1.cpp")); + QCOMPARE(jobTester.started().at(2), QStringLiteral("source3.cpp")); + QCOMPARE(jobTester.started().at(3), QStringLiteral("source4.cpp")); + + // test errors parsing ========================================================================= + + static const QStringList stderrOutput1 = { + QStringLiteral("source2.cpp:13:10: warning: unused variable 'check' [-Wunused-variable]"), + QStringLiteral(" auto check = db.checks()[\"returning-void-expression\"];") + }; + + static const QStringList stderrOutput2 = { + QStringLiteral("source3.cpp:248:21: warning: Don't call QList::first() on temporary [-Wclazy-detaching-temporary]"), + QStringLiteral(" auto item = pContext->items().first();"), + QStringLiteral(" ^"), + QStringLiteral("1 warning generated.") + }; + + static const QStringList stderrOutput3 = { + QStringLiteral("source4.cpp:47:9: warning: unused QString [-Wclazy-unused-non-trivial-variable]"), + QStringLiteral(" auto test = QString(\"%1 : %2\").arg(\"a\").arg(\"b\");"), + QStringLiteral(" ^"), + QStringLiteral("source4.cpp:47:47: warning: Use multi-arg instead [-Wclazy-qstring-arg]"), + QStringLiteral(" auto test = QString(\"%1 : %2\").arg(\"a\").arg(\"b\");"), + QStringLiteral(" ^"), + QStringLiteral("2 warnings generated.") + }; + + jobTester.postProcessStderr(stderrOutput1); + QCOMPARE(jobTester.problems().size(), 0); + + jobTester.postProcessStderr(stderrOutput2); + QCOMPARE(jobTester.problems().size(), 1); + + jobTester.postProcessStderr(stderrOutput3); + QCOMPARE(jobTester.problems().size(), 3); + + // test common values + auto problems = jobTester.problems(); + foreach (auto problem, problems) { + QCOMPARE(problem->severity(), KDevelop::IProblem::Warning); + QCOMPARE(problem->source(), KDevelop::IProblem::Plugin); + } + + // test problem description + QCOMPARE(problems[0]->description(), QStringLiteral("Don't call QList::first() on temporary")); + QCOMPARE(problems[1]->description(), QStringLiteral("unused QString")); + QCOMPARE(problems[2]->description(), QStringLiteral("Use multi-arg instead")); + + // test problem location (file) + QCOMPARE(problems[0]->finalLocation().document.str(), QStringLiteral("source3.cpp")); + QCOMPARE(problems[1]->finalLocation().document.str(), QStringLiteral("source4.cpp")); + QCOMPARE(problems[2]->finalLocation().document.str(), QStringLiteral("source4.cpp")); + + // test problem location (line) + QCOMPARE(problems[0]->finalLocation().start().line(), 247); + QCOMPARE(problems[1]->finalLocation().start().line(), 46); + QCOMPARE(problems[2]->finalLocation().start().line(), 46); + + // test problem location (column) + QCOMPARE(problems[0]->finalLocation().start().column(), 20); + QCOMPARE(problems[1]->finalLocation().start().column(), 8); + QCOMPARE(problems[2]->finalLocation().start().column(), 46); +} + +QTEST_GUILESS_MAIN(TestClazyJob) + +#include "test_clazyjob.moc" diff --git a/plugins/clazy/tests/test_clazyjob.h b/plugins/clazy/tests/test_clazyjob.h new file mode 100644 index 0000000000..1a4c3653ee --- /dev/null +++ b/plugins/clazy/tests/test_clazyjob.h @@ -0,0 +1,37 @@ +/* This file is part of KDevelop + + Copyright 2018 Anton Anikin + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#ifndef KDEVCLAZY_JOB_TEST_H +#define KDEVCLAZY_JOB_TEST_H + +#include + +class TestClazyJob : public QObject +{ + Q_OBJECT + +private Q_SLOTS: + void initTestCase(); + void cleanupTestCase(); + + void testJob(); +}; + +#endif diff --git a/plugins/clazy/utils.cpp b/plugins/clazy/utils.cpp new file mode 100644 index 0000000000..aa9c7cb25f --- /dev/null +++ b/plugins/clazy/utils.cpp @@ -0,0 +1,250 @@ +/* This file is part of KDevelop + + Copyright 2018 Anton Anikin + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#include "utils.h" + +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include + +namespace Clazy +{ + +QString prettyPathName(const QString& path) +{ + return KDevelop::ICore::self()->projectController()->prettyFileName( + QUrl::fromLocalFile(path), + KDevelop::IProjectController::FormatPlain); +} + +QStringList compileCommandsFiles(const QString& jsonFilePath, QString& error) +{ + QStringList paths; + + QFile jsonFile(jsonFilePath); + if (!jsonFile.open(QFile::ReadOnly | QFile::Text)) { + error = i18n("Unable to open compile commands file '%1' for reading", jsonFilePath); + return paths; + } + + QJsonParseError jsonError; + auto document = QJsonDocument::fromJson(jsonFile.readAll(), &jsonError); + + if (jsonError.error) { + error = i18n("JSON error during parsing compile commands file '%1': %2", jsonFilePath, jsonError.errorString()); + return paths; + } + + if (!document.isArray()) { + error = i18n("JSON error during parsing compile commands file '%1': document is not an array", jsonFilePath); + return paths; + } + + const QString KEY_FILE = QStringLiteral("file"); + + const auto array = document.array(); + for (const auto& value : array) { + if (!value.isObject()) { + continue; + } + + const QJsonObject entry = value.toObject(); + if (entry.contains(KEY_FILE)) { + auto path = entry[KEY_FILE].toString(); + if (QFile::exists(path)) + { + paths += path; + } + } + } + + return paths; +} + +// Very simple Markdown parser/converter. Does not provide full Markdown language support and +// was tested only with Clazy documentation. +class MarkdownConverter +{ +public: + MarkdownConverter() + { + tagStart.resize(STATE_COUNT); + tagEnd.resize(STATE_COUNT); + + tagStart[EMPTY].clear(); + tagEnd [EMPTY].clear(); + + tagStart[HEADING] = QStringLiteral(""); + tagEnd [HEADING] = QStringLiteral(""); + + tagStart[PARAGRAPH] = QStringLiteral("

"); + tagEnd [PARAGRAPH] = QStringLiteral("

"); + + tagStart[PREFORMATTED] = QStringLiteral("
");
+        tagEnd  [PREFORMATTED] = QStringLiteral("
"); + + tagStart[LIST] = QStringLiteral("
  • "); + tagEnd [LIST] = QStringLiteral("
"); + } + + ~MarkdownConverter() = default; + + QString toHtml(const QString& markdown) + { + const QRegularExpression hRE(QStringLiteral("(#+) (.+)")); + QRegularExpressionMatch match; + + state = EMPTY; + html.clear(); + html += QStringLiteral(""); + + auto lines = markdown.split('\n'); + for (auto line : lines) { + if (line.isEmpty()) { + setState(EMPTY); + continue; + } + + if (line.startsWith("#")) { + auto match = hRE.match(line); + if (match.hasMatch()) { + setState(HEADING); + html += match.captured(2); + setState(EMPTY); + if (match.captured(1).size() == 1) { + html += QStringLiteral("
"); + } + } + continue; + } + + if (line.startsWith(QStringLiteral("```"))) { + setState((state == PREFORMATTED) ? EMPTY : PREFORMATTED); + continue; + } + + if (line.startsWith(QStringLiteral(" "))) { + if (state == EMPTY) { + setState(PREFORMATTED); + } + } else if ( + line.startsWith(QStringLiteral("- ")) || + line.startsWith(QStringLiteral("* "))) { + // force close and reopen list - this fixes cases when we don't have + // separator line between items + setState(EMPTY); + setState(LIST); + line = line.mid(2); + } + + if (state == EMPTY) { + setState(PARAGRAPH); + } + + processLine(line); + } + setState(EMPTY); + + html += QStringLiteral(""); + return html.join(QLatin1Char('\n')); + } + +private: + enum STATE { + EMPTY, + HEADING, + PARAGRAPH, + PREFORMATTED, + LIST, + + STATE_COUNT + }; + + void setState(int newState) + { + if (state == newState) { + return; + } + + if (state != EMPTY) { + html += tagEnd[state]; + } + + if (newState != EMPTY) { + html += tagStart[newState]; + } + + state = newState; + } + + void processLine(QString& line) + { + static const QRegularExpression ttRE(QStringLiteral("`([^`]+)`")); + static const QRegularExpression bdRE(QStringLiteral("\\*\\*([^\\*]+)\\*\\*")); + static const QRegularExpression itRE(QStringLiteral("[^\\*]\\*([^\\*]+)\\*[^\\*]")); + + static auto applyRE = [](const QRegularExpression& re, QString& line, const QString& tag) { + auto i = re.globalMatch(line); + while (i.hasNext()) { + auto match = i.next(); + line.replace(match.captured(0), QStringLiteral("<%1>%2").arg(tag, match.captured(1))); + } + }; + + if (state != PREFORMATTED) { + line.replace(QLatin1Char('&'), QLatin1String("&")); + line.replace(QLatin1Char('<'), QLatin1String("<")); + line.replace(QLatin1Char('>'), QLatin1String(">")); + + line.replace(QLatin1Char('\"'), QLatin1String(""")); + line.replace(QLatin1Char('\''), QLatin1String("'")); + + applyRE(ttRE, line, QStringLiteral("tt")); + applyRE(bdRE, line, QStringLiteral("b")); + applyRE(itRE, line, QStringLiteral("i")); + } + + html += line; + } + +private: + int state; + QVector tagStart; + QVector tagEnd; + QStringList html; +}; + +QString markdown2html(const QByteArray& markdown) +{ + MarkdownConverter converter; + return converter.toHtml(markdown); +} + +} diff --git a/plugins/clazy/utils.h b/plugins/clazy/utils.h new file mode 100644 index 0000000000..4763a21192 --- /dev/null +++ b/plugins/clazy/utils.h @@ -0,0 +1,37 @@ +/* This file is part of KDevelop + + Copyright 2018 Anton Anikin + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; see the file COPYING. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#ifndef KDEVCLAZY_UTILS_H +#define KDEVCLAZY_UTILS_H + +#include + +namespace Clazy +{ + +QString prettyPathName(const QString& path); + +QStringList compileCommandsFiles(const QString& jsonFilePath, QString& error); + +QString markdown2html(const QByteArray& markdown); + +} + +#endif