summaryrefslogtreecommitdiff
path: root/lisp/cedet/ede/detect.el
blob: c933fc4515e974b8f8f2278a5b48623098568b46 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
;;; ede/detect.el --- EDE project detection and file associations  -*- lexical-binding: t; -*-

;; Copyright (C) 2014-2021 Free Software Foundation, Inc.

;; Author: Eric M. Ludlam <zappo@gnu.org>

;; 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:
;;
;; Project detection for EDE;
;;
;; Detection comes in multiple forms:
;;
;; `ede--detect-scan-directory-for-project' -
;;        Scan for a project via the file system.
;; `ede-detect-directory-for-project' -
;;        Check our file cache for a project.  If that fails, use
;;        the scan fcn above.

;;; Code:

(require 'ede/auto) ;; Autoload settings.

;;; BASIC PROJECT SCAN
;;
(defun ede--detect-stop-scan-p (dir)
  "Return non-nil if we need to stop scanning upward in DIR."
  ;;(let ((stop
  (file-exists-p (expand-file-name ".ede_stop_scan" dir)))
;;)
;;(when stop
;;(message "Stop Scan at %s" dir))
;;stop))

(defvar ede--detect-found-project nil
  "When searching for a project, temporarily save that file.")

(defun ede--detect-ldf-predicate (dir)
  "Non-nil if DIR contain any known EDE project types."
  (if (ede--detect-stop-scan-p dir)
      (throw 'stopscan nil)
    (let ((types ede-project-class-files))
      ;; Loop over all types, loading in the first type that we find.
      (while (and types (not ede--detect-found-project))
	(if (ede-auto-detect-in-dir (car types) dir)
	    (progn
	      ;; We found one!
	      (setq ede--detect-found-project (car types)))
	  (setq types (cdr types)))
	)
      ede--detect-found-project)))

(defun ede--detect-scan-directory-for-project (directory)
  "Detect an EDE project for the current DIRECTORY by scanning.
This function ALWAYS scans files and directories and DOES NOT
use any file caches.
Return a cons cell:
  ( ROOTDIR . PROJECT-AUTOLOAD)"
  (let* ((ede--detect-found-project nil)
	 (root
	  (catch 'stopscan
	    (locate-dominating-file directory
				    'ede--detect-ldf-predicate))))
    (when root
      (cons root ede--detect-found-project))))

;;; Root Only project detect
;;
;; For projects that only have a detectable ROOT file, but may in fact
;; contain a generic file such as a Makefile, we need to do a second scan
;; to make sure we don't miss-match.
(defun ede--detect-ldf-rootonly-predicate (dir)
  "Non-nil if DIR contain any known EDE project types."
  (if (ede--detect-stop-scan-p dir)
      (throw 'stopscan nil)
    (let ((types ede-project-class-files))
      ;; Loop over all types, loading in the first type that we find.
      (while (and types (not ede--detect-found-project))
	(if (and
	     (oref (car types) root-only)
	     (ede-auto-detect-in-dir (car types) dir))
	    (progn
	      ;; We found one!
	      (setq ede--detect-found-project (car types)))
	  (setq types (cdr types)))
	)
      ede--detect-found-project)))

(defun ede--detect-scan-directory-for-rootonly-project (directory)
  "Detect an EDE project for the current DIRECTORY by scanning.
This function ALWAYS scans files and directories and DOES NOT
use any file caches.
Return a cons cell:
  ( ROOTDIR . PROJECT-AUTOLOAD)"
  (let* ((ede--detect-found-project nil)
	 (root
	  (catch 'stopscan
	    (locate-dominating-file directory
				    'ede--detect-ldf-rootonly-predicate))))
    (when root
      (cons root ede--detect-found-project))))


;;; NESTED PROJECT SCAN
;;
;; For projects that can have their dominating file exist in all their
;; sub-directories as well.

(defvar ede--detect-nomatch-auto nil
  "An ede autoload that needs to be un-matched.")

(defun ede--detect-ldf-root-predicate (dir)
  "Non-nil if DIR no longer match `ede--detect-nomatch-auto'."
  ;; `dir' may be "~/".
  (setq dir (expand-file-name dir))
  (or (ede--detect-stop-scan-p dir)
      ;; To know if DIR is at the top, we need to look just above
      ;; to see if there is a match.
      (let ((updir (file-name-directory (directory-file-name dir))))
	(if (equal updir dir)
	    ;; If it didn't change, then obviously this must be the top.
	    t
	  ;; If it is different, check updir for the file.
	  (not (ede-auto-detect-in-dir ede--detect-nomatch-auto updir))))))

(defun ede--detect-scan-directory-for-project-root (directory auto)
  "If DIRECTORY has already been detected with AUTO, find the root.
Some projects have their dominating file in all their directories, such
as Project.ede.  In that case we will detect quickly, but then need
to scan upward to find the topmost occurrence of that file."
  (let* ((ede--detect-nomatch-auto auto)
	 (root (locate-dominating-file directory
				       'ede--detect-ldf-root-predicate)))
    root))

;;; TOP LEVEL SCAN
;;
;; This function for combining the above scans.
(defun ede-detect-directory-for-project (directory)
  "Detect an EDE project for the current DIRECTORY.
Scan the filesystem for a project.
Return a cons cell:
  ( ROOTDIR . PROJECT-AUTOLOAD)"
  (let* ((scan (ede--detect-scan-directory-for-project directory))
	 (root (car scan))
	 (auto (cdr scan)))
    (when scan
      ;; If what we found is already a root-only project, return it.
      (if (oref auto root-only)
	  scan

	;; If what we found is a generic project, check to make sure we aren't
	;; in some other kind of root project.
	(if (oref auto generic-p)
	    (let ((moreroot (ede--detect-scan-directory-for-rootonly-project root)))
	      ;; If we found a rootier project, return that.
	      (if moreroot
		  moreroot

		;; If we didn't find a root from the generic project, then
		;; we need to rescan upward.
		(cons (ede--detect-scan-directory-for-project-root root auto) auto)))

	  ;; Non-generic non-root projects also need to rescan upward.
	  (cons (ede--detect-scan-directory-for-project-root root auto) auto)))

	  )))

;;; TEST
;;
;; A quick interactive testing fcn.
(defun ede-detect-qtest ()
  "Run a quick test for autodetecting on BUFFER."
  (interactive)
  (let ((start (current-time))
	(ans (ede-detect-directory-for-project default-directory)))
    (if ans
	(message "Project found in %d sec @ %s of type %s"
		 (time-convert (time-since start) 'integer)
		 (car ans)
		 (eieio-object-name-string (cdr ans)))
      (message "No Project found.") )))


(provide 'ede/detect)

;;; ede/detect.el ends here