161 lines
6.1 KiB
EmacsLisp
161 lines
6.1 KiB
EmacsLisp
;;; flymake-cc.el --- Flymake support for GNU tools for C/C++ -*- lexical-binding: t; -*-
|
|
|
|
;; Copyright (C) 2018-2024 Free Software Foundation, Inc.
|
|
|
|
;; Author: João Távora <joaotavora@gmail.com>
|
|
;; Keywords: languages, c
|
|
|
|
;; This file is part of GNU Emacs.
|
|
|
|
;; GNU Emacs 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 3 of the License, or
|
|
;; (at your option) any later version.
|
|
|
|
;; GNU Emacs 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 GNU Emacs. If not, see <https://www.gnu.org/licenses/>.
|
|
|
|
;;; Commentary:
|
|
|
|
;; Flymake support for C/C++.
|
|
|
|
;;; Code:
|
|
|
|
(require 'cl-lib)
|
|
|
|
(defcustom flymake-cc-command 'flymake-cc-use-special-make-target
|
|
"Command used by the `flymake-cc' backend.
|
|
A list of strings, or a symbol naming a function that produces one
|
|
such list when called with no arguments in the buffer where the
|
|
variable `flymake-mode' is active.
|
|
|
|
The command should invoke a GNU-style compiler that checks the
|
|
syntax of a (Obj)C(++) program passed to it via its standard
|
|
input and prints the result on its standard output."
|
|
:type '(choice
|
|
(symbol :tag "Function")
|
|
(repeat :tag "Command(s)" string))
|
|
:version "27.1"
|
|
:group 'flymake-cc)
|
|
|
|
(defun flymake-cc--make-diagnostics (source)
|
|
"Parse GNU-compatible compilation messages in current buffer.
|
|
Return a list of Flymake diagnostic objects for the source buffer
|
|
SOURCE."
|
|
;; TODO: if you can understand it, use `compilation-mode's regexps
|
|
;; or even some of its machinery here.
|
|
;;
|
|
;; (setq-local compilation-locs
|
|
;; (make-hash-table :test 'equal :weakness 'value))
|
|
;; (compilation-parse-errors (point-min) (point-max)
|
|
;; 'gnu 'gcc-include)
|
|
;; (while (next-single-property-change 'compilation-message)
|
|
;; ...)
|
|
;;
|
|
;; For now, this works minimally well.
|
|
(cl-loop
|
|
while
|
|
(search-forward-regexp
|
|
"^\\(In file included from \\)?\\([^ :]+\\):\\([0-9]+\\)\\(?::\\([0-9]+\\)\\)?:\n?\\(.*\\): \\(.*\\)$"
|
|
nil t)
|
|
for msg = (match-string 6)
|
|
for locus = (match-string 2)
|
|
for line = (string-to-number (match-string 3))
|
|
for col = (ignore-errors (string-to-number (match-string 4)))
|
|
for source-buffer = (and (string= locus "<stdin>") source)
|
|
for type = (if (match-string 1)
|
|
:error
|
|
(save-match-data
|
|
(assoc-default
|
|
(match-string 5)
|
|
'(("error" . :error)
|
|
("note" . :note)
|
|
("warning" . :warning))
|
|
#'string-match
|
|
:error)))
|
|
for diag =
|
|
(cond (source-buffer
|
|
(pcase-let ((`(,beg . ,end)
|
|
(flymake-diag-region source-buffer line col)))
|
|
(flymake-make-diagnostic source-buffer beg end type msg)))
|
|
(t (flymake-make-diagnostic locus (cons line col) nil type msg)))
|
|
collect diag
|
|
;; If "In file included from..." matched, then move to end of that
|
|
;; line. This helps us collect the diagnostic at its .h locus,
|
|
;; too.
|
|
when (match-end 1) do (goto-char (match-end 2))))
|
|
|
|
(defun flymake-cc-use-special-make-target ()
|
|
"Command for checking a file via a CHK_SOURCES Make target."
|
|
(unless (executable-find "make") (error "Make not found"))
|
|
`("make"
|
|
"check-syntax"
|
|
,(format "CHK_SOURCES=-x %s -c -"
|
|
(cond ((derived-mode-p 'c++-mode) "c++")
|
|
(t "c")))))
|
|
|
|
(defvar-local flymake-cc--proc nil "Internal variable for `flymake-cc'.")
|
|
|
|
;; forward declare this to shoosh compiler (instead of requiring
|
|
;; flymake-proc)
|
|
;;
|
|
(defvar flymake-proc-allowed-file-name-masks)
|
|
|
|
;;;###autoload
|
|
(defun flymake-cc (report-fn &rest _args)
|
|
"Flymake backend for GNU-style C compilers.
|
|
This backend uses `flymake-cc-command' (which see) to launch a
|
|
process that is passed the current buffer's contents via stdin.
|
|
REPORT-FN is Flymake's callback."
|
|
;; HACK: XXX: Assuming this backend function is run before it in
|
|
;; `flymake-diagnostic-functions', very hackingly convince the other
|
|
;; backend `flymake-proc-legacy-flymake', which is on by default, to
|
|
;; disable itself.
|
|
;;
|
|
(setq-local flymake-proc-allowed-file-name-masks nil)
|
|
(when (process-live-p flymake-cc--proc)
|
|
(kill-process flymake-cc--proc))
|
|
(let ((source (current-buffer)))
|
|
(save-restriction
|
|
(widen)
|
|
(setq
|
|
flymake-cc--proc
|
|
(make-process
|
|
:name "gcc-flymake"
|
|
:buffer (generate-new-buffer "*gcc-flymake*")
|
|
:command (if (symbolp flymake-cc-command)
|
|
(funcall flymake-cc-command)
|
|
flymake-cc-command)
|
|
:noquery t :connection-type 'pipe
|
|
:sentinel
|
|
(lambda (p _ev)
|
|
(unwind-protect
|
|
(when (eq 'exit (process-status p))
|
|
(when (with-current-buffer source (eq p flymake-cc--proc))
|
|
(with-current-buffer (process-buffer p)
|
|
(goto-char (point-min))
|
|
(let ((diags
|
|
(flymake-cc--make-diagnostics source)))
|
|
(if (or diags (zerop (process-exit-status p)))
|
|
(funcall report-fn diags)
|
|
;; non-zero exit with no diags is cause
|
|
;; for alarm
|
|
(funcall report-fn
|
|
:panic :explanation
|
|
(buffer-substring
|
|
(point-min) (progn (goto-char (point-min))
|
|
(line-end-position)))))))))
|
|
(unless (process-live-p p)
|
|
;; (display-buffer (process-buffer p)) ; uncomment to debug
|
|
(kill-buffer (process-buffer p)))))))
|
|
(process-send-region flymake-cc--proc (point-min) (point-max))
|
|
(process-send-eof flymake-cc--proc))))
|
|
|
|
(provide 'flymake-cc)
|
|
;;; flymake-cc.el ends here
|