Fix finding C headers with clang

This fixes following "#include" references in 'M-x man' buffers, and
using ffap, both on macOS machines, and on systems where for some reason
clang is available but gcc is not.

* lisp/subr.el (internal--c-header-file-path): Fix finding C
headers with clang.
(internal--gcc-is-clang-p): New function factored out from...
* lisp/emacs-lisp/ert-x.el (ert-gcc-is-clang-p): ...here.
* lisp/man.el (Man-header-file-path): Bump :version tag.

* test/lisp/subr-tests.el
(subr-tests-internal--c-header-file-path/clang-mocked): New test.
This commit is contained in:
Stefan Kangas 2025-01-06 12:29:21 +01:00
parent 888ff3755d
commit 3dc3dbc2e3
4 changed files with 82 additions and 16 deletions

View file

@ -526,11 +526,7 @@ The same keyword arguments are supported as in
(defun ert-gcc-is-clang-p ()
"Return non-nil if the `gcc' command actually runs the Clang compiler."
;; Some macOS machines run llvm when you type gcc. (!)
;; We can't even check if it's a symlink; it's a binary placed in
;; "/usr/bin/gcc". So we need to check the output.
(string-match "Apple \\(LLVM\\|[Cc]lang\\)\\|Xcode\\.app"
(shell-command-to-string "gcc --version")))
(internal--gcc-is-clang-p))
(defvar tramp-default-host-alist)
(defvar tramp-methods)

View file

@ -232,7 +232,7 @@ the associated section number."
(defcustom Man-header-file-path (internal--c-header-file-path)
"C Header file search path used in Man."
:version "24.1" ; add multiarch
:version "31.1"
:type '(repeat string))
(defcustom Man-name-local-regexp (concat "^" (regexp-opt '("NOM" "NAME")) "$")

View file

@ -7556,6 +7556,17 @@ and return the value found in PLACE instead."
,(funcall setter val)
,val)))))
(defun internal--gcc-is-clang-p ()
"Return non-nil if the `gcc' command actually runs the Clang compiler."
;; Recent macOS machines run llvm when you type gcc by default. (!)
;; We can't even check if it's a symlink; it's a binary placed in
;; "/usr/bin/gcc". So we need to check the output.
(when-let* ((out (ignore-errors
(with-temp-buffer
(call-process "gcc" nil t nil "--version")
(buffer-string)))))
(string-match "Apple \\(LLVM\\|[Cc]lang\\)\\|Xcode\\.app" out)))
(defun internal--c-header-file-path ()
"Return search path for C header files (a list of strings)."
;; FIXME: It's not clear that this is a good place to put this, or
@ -7563,15 +7574,41 @@ and return the value found in PLACE instead."
;; See also (Bug#10702):
;; cc-search-directories, semantic-c-dependency-system-include-path,
;; semantic-gcc-setup
(let ((arch (with-temp-buffer
(when (eq 0 (ignore-errors
(call-process "gcc" nil '(t nil) nil
"-print-multiarch")))
(goto-char (point-min))
(buffer-substring (point) (line-end-position)))))
(base '("/usr/include" "/usr/local/include")))
(if (zerop (length arch))
base
(append base (list (expand-file-name arch "/usr/include"))))))
(delete-dups
(let ((base '("/usr/include" "/usr/local/include")))
(cond ((or (internal--gcc-is-clang-p)
(and (executable-find "clang")
(not (executable-find "gcc"))))
;; This is either macOS, or a system with clang only.
(with-temp-buffer
(ignore-errors
(call-process (if (internal--gcc-is-clang-p) "gcc" "clang")
nil t nil
"-v" "-E" "-"))
(goto-char (point-min))
(narrow-to-region
(save-excursion
(re-search-forward
"^#include <\\.\\.\\.> search starts here:\n" nil t)
(point))
(save-excursion
(re-search-forward "^End of search list.$" nil t)
(pos-bol)))
(while (search-forward "(framework directory)" nil t)
(delete-line))
(append base
(reverse
(split-string (buffer-substring-no-properties
(point-min) (point-max)))))))
;; Prefer GCC.
((let ((arch (with-temp-buffer
(when (eq 0 (ignore-errors
(call-process "gcc" nil '(t nil) nil
"-print-multiarch")))
(goto-char (point-min))
(buffer-substring (point) (line-end-position))))))
(if (zerop (length arch))
base
(append base (list (expand-file-name arch "/usr/include"))))))))))
;;; subr.el ends here

View file

@ -1410,5 +1410,38 @@ final or penultimate step during initialization."))
(insert "x86_64-linux-gnu\n") 0))))
(should (member "/usr/include/x86_64-linux-gnu" (internal--c-header-file-path)))))
(ert-deftest subr-tests-internal--c-header-file-path/clang-mocked ()
;; Handle clang 15.0.0 output on macOS 15.2.
(cl-letf (((symbol-function 'internal--gcc-is-clang-p) (lambda () t))
((symbol-function 'call-process)
(lambda (_program &optional _infile _destination _display &rest _args)
(insert "\
Apple clang version 15.0.0 (clang-1500.3.9.4)
Target: arm64-apple-darwin24.2.0
Thread model: posix
InstalledDir: /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin
\"/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/clang\"
[[[...Emacs test omits some verbose junk from the output here...]]]
clang -cc1 version 15.0.0 (clang-1500.3.9.4) default target arm64-apple-darwin24.2.0
ignoring nonexistent directory \"/usr/local/include\"
#include \"...\" search starts here:
#include <...> search starts here:
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/clang/15.0.0/include
/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include
/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/System/Library/Frameworks (framework directory)
End of search list.
# 1 \"<stdin>\"
# 1 \"<built-in>\" 1
# 1 \"<built-in>\" 3
# 418 \"<built-in>\" 3
# 1 \"<command line>\" 1
# 1 \"<built-in>\" 2
# 1 \"<stdin>\" 2")
0)))
(should (member "/usr/include" (internal--c-header-file-path)))
(should (member "/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/clang/15.0.0/include"
(internal--c-header-file-path)))))
(provide 'subr-tests)
;;; subr-tests.el ends here