From ac73ff7148671a3628d76286ab3d5a7d47448695 Mon Sep 17 00:00:00 2001 From: Abdelhak Bougouffa Date: Sat, 31 Dec 2022 20:49:39 +0100 Subject: [PATCH 1/4] gcov: add support for CMake-based projects For CMake projects, the `.gcov` files aren't stored beside the source file, instead, they are placed next to the generated object files (in CMake projects, it will be somewhere like `path/to/build/dir/CMakeFiles/target.src/path/to/file/file.cpp.gcov`) Also, as CMake generates object files as `file.cpp.o` instead of `file.o`, gcov generates a file based on that `file.cpp.gcov` and not `file.gcov`. I've added a function which leverages the information included in the `compile_commands.json`, it extracts the build directory and build command for the file, and then constructs the path to the `.gcov` file which will be next to the `.o` file. --- cov.el | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/cov.el b/cov.el index a03f011..678fa14 100644 --- a/cov.el +++ b/cov.el @@ -141,7 +141,7 @@ COVERAGE-TOOL has created the data. Currently the only supported COVERAGE-TOOL is gcov.") (defvar cov-coverage-file-paths - '("." cov--locate-lcov cov--locate-coveralls cov--locate-clover cov--locate-coveragepy) + '("." cov--locate-gcov-cmake cov--locate-lcov cov--locate-coveralls cov--locate-clover cov--locate-coveragepy) "List of paths or functions returning file paths containing coverage files. Relative paths: @@ -260,6 +260,26 @@ that filename. Otherwise search for the first matching pattern in cov-lcov-patterns))) (when lcov-file (cons lcov-file 'lcov)))) +(defun cov--locate-gcov-cmake (file-dir file-name) + "Locate a gcov coverage file from FILE-DIR for FILE-NAME. +This works with CMake-based projects, by constructing the path to the `.gcov' +file from object file's path extracted from the \"compile_commands.json\"." + (when-let* ((root (project-root (project-current))) + (compile-commands (expand-file-name "compile_commands.json" root))) + (when (file-exists-p compile-commands) + (when-let* ((file-cmd (cl-find-if + (lambda (entry) + (equal (file-truename (alist-get 'file entry)) + (expand-file-name file-name file-dir))) + (json-read-file compile-commands))) + (command (alist-get 'command file-cmd)) + (directory (alist-get 'directory file-cmd))) + (let* ((obj-file (when (string-match + (concat "-o \\(?1:.*" (regexp-quote (concat file-name ".o")) "\\)") + command) + (match-string-no-properties 1 command)))) + (cons (expand-file-name (file-name-with-extension obj-file ".gcov") directory) 'gcov)))))) + (defun cov--locate-coveralls (file-dir _file-name) "Locate coveralls coverage from FILE-DIR for FILE-NAME. From 44c2f0bd591fc4bcb5c66033ba28ab9ff80f7341 Mon Sep 17 00:00:00 2001 From: Abdelhak Bougouffa Date: Sat, 31 Dec 2022 21:35:39 +0100 Subject: [PATCH 2/4] Add an option to highlight line background --- cov.el | 77 +++++++++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 66 insertions(+), 11 deletions(-) diff --git a/cov.el b/cov.el index 678fa14..69429bb 100644 --- a/cov.el +++ b/cov.el @@ -40,6 +40,7 @@ (require 'subr-x) (require 'filenotify) (require 'elquery) +(require 'color) (defgroup cov nil "The group for everything in cov.el." @@ -89,6 +90,16 @@ See `fringe-bitmaps' for a full list of options" :group 'cov :type 'symbol) +(defcustom cov-highlight-lines nil + "Hightlight lines." + :group 'cov + :type 'bool) + +(defun cov--get-highlight-color (face) + "Return a brighter color from FACE foreground." + (let ((color (apply #'color-rgb-to-hsl (color-name-to-rgb (face-foreground face))))) + (apply #'color-rgb-to-hex (color-hsl-to-rgb (nth 0 color) (nth 1 color) 0.95)))) + (defface cov-heavy-face '((((class color)) :foreground "red")) "Fringe indicator face used for heavily-run lines See `cov-high-threshold'." @@ -130,6 +141,47 @@ See `cov-coverage-mode'" :tag "Cov coverage mode not-run face" :group 'cov-faces) +(defface cov-heavy-hl-face + `((((class color)) :background ,(cov--get-highlight-color 'cov-heavy-face))) + "Hightlight face used for heavily-run lines See `cov-high-threshold'." + :tag "Cov heavy-use face" + :group 'cov-faces) + +(defface cov-med-hl-face + `((((class color)) :background ,(cov--get-highlight-color 'cov-med-face))) + "Hightlight face used for commonly-run lines See `cov-med-threshold'." + :tag "Cov medium-use face" + :group 'cov-faces) + +(defface cov-light-hl-face + `((((class color)) :background ,(cov--get-highlight-color 'cov-light-face))) + "Hightlight face used for rarely-run lines. + +This face is applied if no other face is applied." + :tag "Cov light-use face" + :group 'cov-faces) + +(defface cov-none-hl-face + `((((class color)) :background ,(cov--get-highlight-color 'cov-none-face))) + "Hightlight face used for lines which were not run." + :tag "Cov never-used face" + :group 'cov-faces) + +(defface cov-coverage-run-hl-face + `((((class color)) :background ,(cov--get-highlight-color 'cov-coverage-run-face))) + "Hightlight face used in coverage mode for lines which were run. + +See `cov-coverage-mode'" + :tag "Cov coverage mode run face" + :group 'cov-faces) + +(defface cov-coverage-not-run-hl-face + `((((class color)) :background ,(cov--get-highlight-color 'cov-coverage-not-run-face))) + "Hightlight face used in coverage mode for lines which were not run. +See `cov-coverage-mode'" + :tag "Cov coverage mode not-run face" + :group 'cov-faces) + (defvar cov-coverage-alist '((".gcov" . gcov)) "Alist of coverage tool and file postfix. @@ -552,7 +604,7 @@ Load FILE-PATH into temp buffer and parse it using `cov--FORMAT-parse'. (setq-local cov-coverage-file file-path) (funcall (intern (concat "cov--" (symbol-name format) "-parse"))))) -(defun cov--make-overlay (line fringe help) +(defun cov--make-overlay (line fringe help highlight) "Create an overlay for the LINE. Uses the FRINGE and sets HELP as `help-echo'." @@ -562,6 +614,8 @@ Uses the FRINGE and sets HELP as `help-echo'." (make-overlay (point) (line-end-position))))) (overlay-put ol 'before-string fringe) (overlay-put ol 'help-echo help) + (when cov-highlight-lines + (overlay-put ol 'face highlight)) (overlay-put ol 'cov t) ol)) @@ -572,20 +626,20 @@ Selects the face depending on user preferences and the code's execution frequency" (cond ((and cov-coverage-mode (> percentage 0)) - 'cov-coverage-run-face) + '(cov-coverage-run-face . cov-coverage-run-hl-face)) ((and cov-coverage-mode (= percentage 0)) - 'cov-coverage-not-run-face) + '(cov-coverage-not-run-face . cov-coverage-not-run-hl-face)) ((< cov-high-threshold percentage) - 'cov-heavy-face) + '(cov-heavy-face . cov-heavy-hl-face)) ((< cov-med-threshold percentage) - 'cov-med-face) + '(cov-med-face . cov-med-hl-face)) ((> percentage 0) - 'cov-light-face) - (t 'cov-none-face))) + '(cov-light-face . cov-light-hl-face)) + (t '(cov-none-face . cov-none-hl-face)))) (defun cov--get-fringe (percentage) "Return the fringe with the correct face for PERCENTAGE." - (propertize "f" 'display `(left-fringe ,cov-fringe-symbol ,(cov--get-face percentage)))) + (propertize "f" 'display `(left-fringe ,cov-fringe-symbol ,(car (cov--get-face percentage))))) (defun cov--help (n percentage) "Return help text for the given N count and PERCENTAGE." @@ -600,7 +654,8 @@ MAX is the maximum coverage count for any line in the file." (cov--make-overlay (car line) (cov--get-fringe percentage) - (cov--help times-executed percentage)))) + (cov--help times-executed percentage) + (cdr (cov--get-face percentage))))) (defsubst cov--file-mtime (file) "Return the last modification time of FILE." @@ -614,8 +669,8 @@ MAX is the maximum coverage count for any line in the file." (mtime nil :type time :documentation "The mtime for the coverage file last time it was read.") (buffers nil :type list :documentation "List of `cov-mode' buffers referring to this coverage data.") (watcher nil :type watcher :documentation "The file notification watcher.") - (coverage nil :type alist :documentation "An alist of (FILE . ((LINE-NUM TIMES-RAN) ...)).") - ) + (coverage nil :type alist :documentation "An alist of (FILE . ((LINE-NUM TIMES-RAN) ...)).")) + (defsubst cov-data--add-buffer (coverage buffer) "Add BUFFER to COVERAGE if it not already there." From f9b4c7b3f23958154e71e2e48be07dfa7afecafd Mon Sep 17 00:00:00 2001 From: Abdelhak Bougouffa Date: Sun, 1 Jan 2023 17:16:52 +0100 Subject: [PATCH 3/4] Ensure compatibility with Emacs 24.4+, better match for object files --- cov.el | 81 ++++++++++++++++++++++++++++++++++------------------------ 1 file changed, 47 insertions(+), 34 deletions(-) diff --git a/cov.el b/cov.el index 69429bb..ba2e30f 100644 --- a/cov.el +++ b/cov.el @@ -316,21 +316,34 @@ that filename. Otherwise search for the first matching pattern in "Locate a gcov coverage file from FILE-DIR for FILE-NAME. This works with CMake-based projects, by constructing the path to the `.gcov' file from object file's path extracted from the \"compile_commands.json\"." - (when-let* ((root (project-root (project-current))) - (compile-commands (expand-file-name "compile_commands.json" root))) - (when (file-exists-p compile-commands) - (when-let* ((file-cmd (cl-find-if - (lambda (entry) - (equal (file-truename (alist-get 'file entry)) - (expand-file-name file-name file-dir))) - (json-read-file compile-commands))) - (command (alist-get 'command file-cmd)) - (directory (alist-get 'directory file-cmd))) - (let* ((obj-file (when (string-match - (concat "-o \\(?1:.*" (regexp-quote (concat file-name ".o")) "\\)") - command) - (match-string-no-properties 1 command)))) - (cons (expand-file-name (file-name-with-extension obj-file ".gcov") directory) 'gcov)))))) + (let* ((root (project-root (project-current))) + (compile-commands (and root (expand-file-name "compile_commands.json" root)))) + (when (and compile-commands (file-exists-p compile-commands)) + (let* ((file-cmd (cl-find-if + (lambda (entry) + (equal (file-truename (cdr (assq 'file entry))) + (expand-file-name file-name file-dir))) + (json-read-file compile-commands))) + (command (cdr (assq 'command file-cmd))) + (directory (cdr (assq 'directory file-cmd)))) + (when file-cmd + ;; Supposing file-name is "main.cpp", we will construct a regexp that matches "main.cpp.o" or "main.o". + (let* ((case-fold-search nil) + (obj-file (when (string-match + (concat + " -o[ ]*\\(?1:.*" + (regexp-quote (file-name-sans-extension file-name)) + "\\(?:\\." + (file-name-extension file-name) + "\\)?" + "\\.o\\)") + command) + (match-string-no-properties 1 command))) + (gcov-file (expand-file-name (file-name-with-extension obj-file ".gcov") directory))) + ;; If file doesn't exists, return nil, so we can try other paths/functions + ;; from `cov-coverage-file-paths' + (when (file-exists-p gcov-file) + (cons gcov-file 'gcov)))))))) (defun cov--locate-coveralls (file-dir _file-name) "Locate coveralls coverage from FILE-DIR for FILE-NAME. @@ -373,17 +386,17 @@ Read from `current-buffer' if BUFFER is nil. Return a list `((FILE . ((LINE-NUM TIMES-RAN) ...)))'. Unused lines (TIMES-RAN '-') are filtered out." (with-current-buffer (or buffer (current-buffer)) - ;; The buffer is _not_ automatically widened. It is possible to - ;; read just a portion of the buffer by narrowing it first. - (let ((line-re (rx line-start - ;; note the group numbers are in reverse order - ;; in the first alternative - (or (seq (* blank) (group-n 2 (+ (in digit ?#))) ?: - (* blank) (group-n 1 (+ digit)) ?:) - (seq "lcount:" (group-n 1 (+ digit)) ?, (group-n 2 (+ digit)))))) - ;; Derive the name of the covered file from the filename of - ;; the coverage file. - (filename (file-name-sans-extension (f-filename cov-coverage-file)))) + ;; The buffer is _not_ automatically widened. It is possible to + ;; read just a portion of the buffer by narrowing it first. + (let ((line-re (rx line-start + ;; note the group numbers are in reverse order + ;; in the first alternative + (or (seq (* blank) (group-n 2 (+ (in digit ?#))) ?: + (* blank) (group-n 1 (+ digit)) ?:) + (seq "lcount:" (group-n 1 (+ digit)) ?, (group-n 2 (+ digit)))))) + ;; Derive the name of the covered file from the filename of + ;; the coverage file. + (filename (file-name-sans-extension (f-filename cov-coverage-file)))) ;; Replace the filename with the one from the file preamble ;; `Source' tag or `file' line in the intermediate format. ;; TODO: The intermediate format actually support multiple @@ -405,14 +418,14 @@ Read from `current-buffer' if BUFFER is nil. Return a list (file-truename (expand-file-name (match-string 1) (file-name-directory cov-coverage-file)))))))) - (save-excursion - (save-match-data - (goto-char (point-min)) - (list (cons filename - (cl-loop - while (re-search-forward line-re nil t) - collect (list (string-to-number (match-string 1)) - (string-to-number (match-string 2))))))))))) + (save-excursion + (save-match-data + (goto-char (point-min)) + (list (cons filename + (cl-loop + while (re-search-forward line-re nil t) + collect (list (string-to-number (match-string 1)) + (string-to-number (match-string 2))))))))))) (defconst cov--lcov-prefix-re (rx line-start From 933e3d13673b2c0b5408364a77bbe085cbd96476 Mon Sep 17 00:00:00 2001 From: Abdelhak Bougouffa Date: Sun, 1 Jan 2023 17:50:02 +0100 Subject: [PATCH 4/4] Revert "Add an option to highlight line background" This reverts commit 44c2f0bd591fc4bcb5c66033ba28ab9ff80f7341. --- cov.el | 77 +++++++++------------------------------------------------- 1 file changed, 11 insertions(+), 66 deletions(-) diff --git a/cov.el b/cov.el index ba2e30f..ce24d38 100644 --- a/cov.el +++ b/cov.el @@ -40,7 +40,6 @@ (require 'subr-x) (require 'filenotify) (require 'elquery) -(require 'color) (defgroup cov nil "The group for everything in cov.el." @@ -90,16 +89,6 @@ See `fringe-bitmaps' for a full list of options" :group 'cov :type 'symbol) -(defcustom cov-highlight-lines nil - "Hightlight lines." - :group 'cov - :type 'bool) - -(defun cov--get-highlight-color (face) - "Return a brighter color from FACE foreground." - (let ((color (apply #'color-rgb-to-hsl (color-name-to-rgb (face-foreground face))))) - (apply #'color-rgb-to-hex (color-hsl-to-rgb (nth 0 color) (nth 1 color) 0.95)))) - (defface cov-heavy-face '((((class color)) :foreground "red")) "Fringe indicator face used for heavily-run lines See `cov-high-threshold'." @@ -141,47 +130,6 @@ See `cov-coverage-mode'" :tag "Cov coverage mode not-run face" :group 'cov-faces) -(defface cov-heavy-hl-face - `((((class color)) :background ,(cov--get-highlight-color 'cov-heavy-face))) - "Hightlight face used for heavily-run lines See `cov-high-threshold'." - :tag "Cov heavy-use face" - :group 'cov-faces) - -(defface cov-med-hl-face - `((((class color)) :background ,(cov--get-highlight-color 'cov-med-face))) - "Hightlight face used for commonly-run lines See `cov-med-threshold'." - :tag "Cov medium-use face" - :group 'cov-faces) - -(defface cov-light-hl-face - `((((class color)) :background ,(cov--get-highlight-color 'cov-light-face))) - "Hightlight face used for rarely-run lines. - -This face is applied if no other face is applied." - :tag "Cov light-use face" - :group 'cov-faces) - -(defface cov-none-hl-face - `((((class color)) :background ,(cov--get-highlight-color 'cov-none-face))) - "Hightlight face used for lines which were not run." - :tag "Cov never-used face" - :group 'cov-faces) - -(defface cov-coverage-run-hl-face - `((((class color)) :background ,(cov--get-highlight-color 'cov-coverage-run-face))) - "Hightlight face used in coverage mode for lines which were run. - -See `cov-coverage-mode'" - :tag "Cov coverage mode run face" - :group 'cov-faces) - -(defface cov-coverage-not-run-hl-face - `((((class color)) :background ,(cov--get-highlight-color 'cov-coverage-not-run-face))) - "Hightlight face used in coverage mode for lines which were not run. -See `cov-coverage-mode'" - :tag "Cov coverage mode not-run face" - :group 'cov-faces) - (defvar cov-coverage-alist '((".gcov" . gcov)) "Alist of coverage tool and file postfix. @@ -617,7 +565,7 @@ Load FILE-PATH into temp buffer and parse it using `cov--FORMAT-parse'. (setq-local cov-coverage-file file-path) (funcall (intern (concat "cov--" (symbol-name format) "-parse"))))) -(defun cov--make-overlay (line fringe help highlight) +(defun cov--make-overlay (line fringe help) "Create an overlay for the LINE. Uses the FRINGE and sets HELP as `help-echo'." @@ -627,8 +575,6 @@ Uses the FRINGE and sets HELP as `help-echo'." (make-overlay (point) (line-end-position))))) (overlay-put ol 'before-string fringe) (overlay-put ol 'help-echo help) - (when cov-highlight-lines - (overlay-put ol 'face highlight)) (overlay-put ol 'cov t) ol)) @@ -639,20 +585,20 @@ Selects the face depending on user preferences and the code's execution frequency" (cond ((and cov-coverage-mode (> percentage 0)) - '(cov-coverage-run-face . cov-coverage-run-hl-face)) + 'cov-coverage-run-face) ((and cov-coverage-mode (= percentage 0)) - '(cov-coverage-not-run-face . cov-coverage-not-run-hl-face)) + 'cov-coverage-not-run-face) ((< cov-high-threshold percentage) - '(cov-heavy-face . cov-heavy-hl-face)) + 'cov-heavy-face) ((< cov-med-threshold percentage) - '(cov-med-face . cov-med-hl-face)) + 'cov-med-face) ((> percentage 0) - '(cov-light-face . cov-light-hl-face)) - (t '(cov-none-face . cov-none-hl-face)))) + 'cov-light-face) + (t 'cov-none-face))) (defun cov--get-fringe (percentage) "Return the fringe with the correct face for PERCENTAGE." - (propertize "f" 'display `(left-fringe ,cov-fringe-symbol ,(car (cov--get-face percentage))))) + (propertize "f" 'display `(left-fringe ,cov-fringe-symbol ,(cov--get-face percentage)))) (defun cov--help (n percentage) "Return help text for the given N count and PERCENTAGE." @@ -667,8 +613,7 @@ MAX is the maximum coverage count for any line in the file." (cov--make-overlay (car line) (cov--get-fringe percentage) - (cov--help times-executed percentage) - (cdr (cov--get-face percentage))))) + (cov--help times-executed percentage)))) (defsubst cov--file-mtime (file) "Return the last modification time of FILE." @@ -682,8 +627,8 @@ MAX is the maximum coverage count for any line in the file." (mtime nil :type time :documentation "The mtime for the coverage file last time it was read.") (buffers nil :type list :documentation "List of `cov-mode' buffers referring to this coverage data.") (watcher nil :type watcher :documentation "The file notification watcher.") - (coverage nil :type alist :documentation "An alist of (FILE . ((LINE-NUM TIMES-RAN) ...)).")) - + (coverage nil :type alist :documentation "An alist of (FILE . ((LINE-NUM TIMES-RAN) ...)).") + ) (defsubst cov-data--add-buffer (coverage buffer) "Add BUFFER to COVERAGE if it not already there."