[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[PATCH] Flymake support for C/C++
From: |
João Távora |
Subject: |
[PATCH] Flymake support for C/C++ |
Date: |
Thu, 12 Oct 2017 16:09:15 +0100 |
User-agent: |
Gnus/5.13 (Gnus v5.13) Emacs/26.0.90 (gnu/linux) |
Hi,
Here's a proposal for supporting Flymake in C/C++. This patch:
- Sets up Flymake in c-mode buffers (heads up Alan), but doesn't
automatically enable it.
- Adds a special target to src/Makefile so that Flymake can work with
Emacs's C sources (using a default cc-flymake-use-special-make-target
method).
- The special target's name is 'check-syntax' and uses CHK_SOURCES,
which lets older Emacsen edit new Emacs sources with old Flymake
implementations (dubious but harmless advantage).
- Is customizable by the user to use a zero-configuration method that
guesses GCC flags from Makefiles (see
cc-flymake-use-cc-directly). This probably works in the simplest
scenarios(*)
- Is programmable by the user to use any other self-configuration
technique (this includes per-file/dir manual configuration using a
file-local cc-flymake-command)
*: Sadly, GNU Hello no longer works since it uses a backquoted shell
expression that the current implementation can't intercept (my old
use-emacs-as-a-shell-parser )
Here it is, for reference
gcc -DLOCALEDIR=\"/usr/local/share/locale\" -DHAVE_CONFIG_H -I. -I. -I.. -I.
-I. -I.. -I../intl -I../intl -g -O2 -c `test -f 'hello.c' || echo
'./'`hello.c
Also, FTR, discovered that -M flags do not seem to harm syntax-checking.
No idea if dependencies files are still created anyway though.
João
>From 41ef8318c8c539e475080e3b240b9c941d39caa9 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jo=C3=A3o=20T=C3=A1vora?= <address@hidden>
Date: Thu, 12 Oct 2017 14:04:51 +0100
Subject: [PATCH] Add a Flymake backend for C(++)
* lisp/progmodes/cc-flymake.el: New file.
* lisp/progmodes/cc-mode.el (c-mode): Call c--setup-flymake.
(c--setup-flymake): New function.
* src/Makefile.in: Add check-syntax target.
---
lisp/progmodes/cc-flymake.el | 214 +++++++++++++++++++++++++++++++++++++++++++
lisp/progmodes/cc-mode.el | 9 ++
src/Makefile.in | 5 +
3 files changed, 228 insertions(+)
create mode 100644 lisp/progmodes/cc-flymake.el
diff --git a/lisp/progmodes/cc-flymake.el b/lisp/progmodes/cc-flymake.el
new file mode 100644
index 0000000000..4baffdd3be
--- /dev/null
+++ b/lisp/progmodes/cc-flymake.el
@@ -0,0 +1,214 @@
+;;; cc-flymake.el --- Flymake backends for C/C++ -*- lexical-binding: t;
-*-
+
+;; Copyright (C) 2017 Free Software Foundation, Inc.
+
+;; Author: João Távora <address@hidden>
+;; Keywords: languages, c
+
+;; 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 3 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. If not, see <https://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;; Flymake support for C/C++.
+
+;;; Code:
+
+(require 'subr-x) ; when-let*
+
+(defcustom cc-flymake-command 'cc-flymake-use-special-make-target
+ "Command used by the CC flymake 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 :) string))
+ :group 'cc-flymake)
+
+(defvar-local cc-flymake--cached-flags nil)
+
+(defun cc-flymake--guess-flags (cc-program &optional trash-cache)
+ "Guess C(++) flags for compiling current buffer.
+Do this with by finding a suitable Makefile and intercepting an
+invocation of CC-PROGRAM, a string naming a C(++) compiler, in
+the output of \"make --just-print <file.o>\".
+
+Cache the result. TRASH-CACHE of a change to the Makefile rests
+the cache.
+
+This function signals an error if it encounters any problems,
+which normally causes using backends to be disabled.
+
+Can also function interactively for debug purposes."
+ (interactive (list (read-from-minibuffer "Compiler? ")
+ current-prefix-arg))
+ (when trash-cache (setq cc-flymake--cached-flags nil))
+ (catch 'retval
+ (unless (buffer-file-name)
+ ;; don't error and don't cache, so that when the buffer is saved
+ ;; we get another chance.
+ (throw 'retval nil))
+ (when-let* ((makefile-dir
+ (locate-dominating-file default-directory "Makefile"))
+ (makefile (expand-file-name "Makefile" makefile-dir))
+ (mtime (file-attribute-modification-time
+ (file-attributes makefile))))
+ (cond
+ ((equal (list cc-program makefile mtime)
+ (cdr cc-flymake--cached-flags))
+ (when (called-interactively-p 'interactive)
+ (message "cache HIT for %s flags: %S" cc-program
+ (car cc-flymake--cached-flags)))
+ (throw 'retval (car cc-flymake--cached-flags)))
+ (t
+ (let*
+ ((sans-nothing
+ (file-name-nondirectory
+ (file-name-sans-extension
+ (buffer-file-name))))
+ (blob (shell-command-to-string
+ (format "make -C %s -f %s --just-print %s.o"
+ makefile-dir
+ makefile
+ sans-nothing)))
+ (match (string-match
+ (format "%s[[:space:]]+\\(\\(?:-.*\\)*\\)%s"
+ cc-program
+ sans-nothing)
+ blob))
+ (flag-string (and match
+ (match-string 1 blob)))
+ (flags (and flag-string
+ ;; FIXME: shell unescaping: Nothing here to
+ ;; deal with simple backslash-escaped spaces,
+ ;; like quoted and backquoted expressions,
+ ;; etc.
+ (split-string
+ flag-string
+ nil
+ nil
+ "[[:space:]]+"))))
+ (when (or flags (string= "" flag-string))
+ (setq cc-flymake--cached-flags
+ (list flags cc-program makefile mtime))
+ (when (called-interactively-p 'interactive)
+ (message "cache MISS for %s flags: %S" cc-program flags))
+ (throw 'retval flags))))))
+ (error "Could not guess %s flags" cc-program)))
+
+(require 'compile)
+(defun cc-flymake--make-diagnostics (source)
+ "Parse the current buffer of compilation messages.
+Return a list of diagnostics for the source buffer SOURCE."
+ ;; TODO: if you can understand it, use `compilation-mode's regexps
+ ;; or even some of its machinery here.
+ ;;
+ ;; (set (make-local-variable '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 minimaly well.
+ (cl-loop
+ while
+ (search-forward-regexp
+ "^\\(In file included from
\\)?<stdin>:\\([0-9]+\\):\\([0-9]+\\):\n?\\(.*\\): \\(.*\\)$"
+ nil t)
+ for msg = (match-string 5)
+ for (beg . end) = (flymake-diag-region
+ source
+ (string-to-number (match-string 2))
+ (string-to-number (match-string 3)))
+ for type = (if (match-string 1)
+ :error
+ (assoc-default
+ (match-string 4)
+ '(("error" . :error)
+ ("note" . :note)
+ ("warning" . :warning))
+ #'string-match))
+ collect (flymake-make-diagnostic source beg end type msg)))
+
+(defun cc-flymake-use-special-make-target ()
+ "Build command for checking a file with Make directly."
+ (unless (executable-find "make") (error "Make not found"))
+ `("make" "check-syntax" "CHK_SOURCES=-x c -"))
+
+(defvar-local cc-flymake-program "cc"
+ "C(++) compiler used by `cc-flymake-use-cc-directly'.")
+
+(defun cc-flymake-use-cc-directly ()
+ "Build command for checking a file with a C(++) compiler."
+ (unless (executable-find cc-flymake-program)
+ (error "%s not found" cc-flymake-program))
+ `(,cc-flymake-program
+ "-fsyntax-only"
+ ,@(cc-flymake--guess-flags cc-flymake-program)
+ "-x" "c" "-"))
+
+(defvar-local cc-flymake--proc nil
+ "Internal variable for `flymake-gcc'")
+
+;;;###autoload
+(defun cc-flymake (report-fn &rest _args)
+ "Flymake backend for GNU-style C compilers.
+This backend uses `cc-flymake-command' (which see) to launch a
+process that is passed the current buffer's contents via stdin.
+REPORT-FN is Flymake's callback."
+ (when (process-live-p cc-flymake--proc)
+ (kill-process cc-flymake--proc))
+ (let ((source (current-buffer)))
+ (save-restriction
+ (widen)
+ (setq
+ cc-flymake--proc
+ (make-process
+ :name "gcc-flymake"
+ :buffer (generate-new-buffer "*gcc-flymake*")
+ :command (if (symbolp cc-flymake-command)
+ (funcall cc-flymake-command)
+ cc-flymake-command)
+ :noquery t :connection-type 'pipe
+ :sentinel
+ (lambda (p _ev)
+ (when (eq 'exit (process-status p))
+ (unwind-protect
+ (when (eq p cc-flymake--proc)
+ (with-current-buffer (process-buffer p)
+ (goto-char (point-min))
+ (let ((diags
+ (cc-flymake--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))))))))
+ ;; (display-buffer (process-buffer p)) ; for debug
+ (kill-buffer (process-buffer p)))))))
+ (process-send-region cc-flymake--proc (point-min) (point-max))
+ (process-send-eof cc-flymake--proc))))
+
+(provide 'cc-flymake)
+;;; cc-flymake.el ends here
diff --git a/lisp/progmodes/cc-mode.el b/lisp/progmodes/cc-mode.el
index b0e5fe47a7..37c8ecfa98 100644
--- a/lisp/progmodes/cc-mode.el
+++ b/lisp/progmodes/cc-mode.el
@@ -1842,6 +1842,7 @@ c-mode
(c-common-init 'c-mode)
(easy-menu-add c-c-menu)
(cc-imenu-init cc-imenu-c-generic-expression)
+ (c--setup-flymake)
(c-run-mode-hooks 'c-mode-common-hook))
(defconst c-or-c++-mode--regexp
@@ -2297,6 +2298,14 @@ c-submit-bug-report
(insert (format "Buffer Style: %s\nc-emacs-features: %s\n"
style c-features)))))))
+(defun c--setup-flymake ()
+ "Setup flymake for cc buffers."
+ (add-hook 'flymake-diagnostic-functions 'cc-flymake nil t)
+ (defvar flymake-proc-allowed-file-name-masks)
+ ;; hackinly convince the legacy `flymake-proc' backend to disable
+ ;; itself.
+ (setq-local flymake-proc-allowed-file-name-masks nil))
+
(cc-provide 'cc-mode)
diff --git a/src/Makefile.in b/src/Makefile.in
index 9a8c9c85f0..66c259902f 100644
--- a/src/Makefile.in
+++ b/src/Makefile.in
@@ -746,3 +746,8 @@ bootstrap-emacs$(EXEEXT):
endif
@: Compile some files earlier to speed up further compilation.
$(MAKE) -C ../lisp compile-first EMACS="$(bootstrap_exe)"
+
+### Flymake support (for C only)
+check-syntax:
+ $(AM_V_CC)$(CC) -c $(CPPFLAGS) $(ALL_CFLAGS) ${CHK_SOURCES} || true
+.PHONY: check-syntax
--
2.11.0
- [PATCH] Flymake support for C/C++,
João Távora <=