unofficial mirror of emacs-devel@gnu.org 
 help / color / mirror / code / Atom feed
* [PATCH] Flymake support for C/C++
@ 2017-10-12 15:09 João Távora
  2017-10-12 15:50 ` Mark Oteiza
                   ` (2 more replies)
  0 siblings, 3 replies; 65+ messages in thread
From: João Távora @ 2017-10-12 15:09 UTC (permalink / raw)
  To: emacs-devel; +Cc: acm, eliz, npostavs, sdl.web, monnier

[-- Attachment #1: Type: text/plain, Size: 1405 bytes --]

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


[-- Attachment #2: 0001-Add-a-Flymake-backend-for-C.patch --]
[-- Type: text/x-diff, Size: 11225 bytes --]

From 41ef8318c8c539e475080e3b240b9c941d39caa9 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jo=C3=A3o=20T=C3=A1vora?= <joaotavora@gmail.com>
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 <joaotavora@gmail.com>
+;; 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))
+
 \f
 (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


^ permalink raw reply related	[flat|nested] 65+ messages in thread

end of thread, other threads:[~2018-06-03 17:02 UTC | newest]

Thread overview: 65+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2017-10-12 15:09 [PATCH] Flymake support for C/C++ João Távora
2017-10-12 15:50 ` Mark Oteiza
2017-10-12 17:50 ` Alan Mackenzie
2017-10-12 18:45   ` Stefan Monnier
2017-10-12 20:45     ` Alan Mackenzie
2017-10-12 21:03       ` Stefan Monnier
2017-10-13  6:28       ` Eli Zaretskii
2017-10-12 18:46   ` João Távora
2017-10-12 20:39     ` Alan Mackenzie
2017-10-12 21:05       ` Stefan Monnier
2017-10-12 21:24       ` João Távora
2018-06-01 21:07         ` Alan Mackenzie
2018-06-01 21:54           ` João Távora
2018-06-01 22:08             ` Stefan Monnier
2018-06-01 23:23               ` Rolf Ade
2018-06-02 10:33             ` Alan Mackenzie
2018-06-02 14:44               ` Stefan Monnier
2018-06-02 18:13               ` João Távora
2018-06-03 15:45                 ` Alan Mackenzie
2018-06-03 16:28                   ` João Távora
2018-06-03 16:43                     ` Alan Mackenzie
2018-06-03 17:02                       ` João Távora
2018-06-02 17:16           ` Stefan Monnier
2018-06-02 15:26   ` Stefan Monnier
2018-06-03 13:44     ` Alan Mackenzie
2017-10-14  1:34 ` Richard Stallman
2017-10-14  7:10   ` Reuben Thomas
2017-10-14  7:58     ` Sami Kerola
2017-10-14  8:00     ` Eli Zaretskii
2017-10-14  8:15       ` Reuben Thomas
2017-10-14  8:22         ` Dmitry Gutov
2017-10-14  8:29           ` Reuben Thomas
2017-10-14 10:36             ` Eli Zaretskii
2017-10-14 11:22               ` Reuben Thomas
2017-10-14  8:33         ` Eli Zaretskii
2017-10-17 10:53           ` Phillip Lord
2017-10-17 10:56             ` Reuben Thomas
2017-10-18  4:03               ` Richard Stallman
2017-10-18 10:18                 ` Reuben Thomas
2017-10-19  3:26                   ` Richard Stallman
2017-10-19  7:38                     ` Reuben Thomas
2017-10-22 23:18                       ` Richard Stallman
2017-10-22 23:23                         ` Reuben Thomas
2017-10-24  4:12                           ` Richard Stallman
2017-10-24  9:45                             ` Reuben Thomas
2017-10-24  9:48                               ` Dmitry Gutov
2017-10-24  9:52                                 ` Reuben Thomas
2017-10-24  9:57                                   ` Dmitry Gutov
2017-10-24 10:07                                     ` Reuben Thomas
2017-10-24 10:21                                       ` Dmitry Gutov
2017-10-24 10:28                                         ` Reuben Thomas
2017-10-24 15:44                                   ` Stefan Monnier
2017-10-25 19:30                               ` Richard Stallman
2017-10-27  0:43                                 ` Reuben Thomas
2017-10-28 21:47                                   ` Richard Stallman
2017-10-18 12:16           ` Clément Pit-Claudel
2017-10-18 17:30             ` John Wiegley
2017-10-14 13:55         ` Stefan Monnier
2017-10-14  9:33     ` João Távora
2017-10-14 10:56       ` guillaume papin
2017-10-14 16:29         ` João Távora
2017-10-14 16:36           ` Reuben Thomas
2017-10-18 12:22           ` Clément Pit-Claudel
2017-10-18 14:26             ` João Távora
2017-10-14  9:29   ` João Távora

Code repositories for project(s) associated with this public inbox

	https://git.savannah.gnu.org/cgit/emacs.git

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).