Commit | Line | Data |
---|---|---|
cb85c0d8 EL |
1 | ;;; ede/auto.el --- Autoload features for EDE |
2 | ||
acaf905b | 3 | ;; Copyright (C) 2010-2012 Free Software Foundation, Inc. |
cb85c0d8 EL |
4 | |
5 | ;; Author: Eric M. Ludlam <zappo@gnu.org> | |
6 | ||
7 | ;; This file is part of GNU Emacs. | |
8 | ||
9 | ;; GNU Emacs is free software: you can redistribute it and/or modify | |
10 | ;; it under the terms of the GNU General Public License as published by | |
11 | ;; the Free Software Foundation, either version 3 of the License, or | |
12 | ;; (at your option) any later version. | |
13 | ||
14 | ;; GNU Emacs is distributed in the hope that it will be useful, | |
15 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of | |
16 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
17 | ;; GNU General Public License for more details. | |
18 | ||
19 | ;; You should have received a copy of the GNU General Public License | |
20 | ;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. | |
21 | ||
22 | ;;; Commentary: | |
23 | ;; | |
24 | ;; EDE Autoloads are a way to refer to different project types without | |
25 | ;; loading those projects into Emacs. | |
26 | ;; | |
27 | ;; These routines are used to detect a project in a filesystem before | |
28 | ;; handing over control to the usual EDE project system. | |
29 | ||
30 | ;;; Code: | |
31 | ||
32 | (require 'eieio) | |
33 | ||
af8556d2 JB |
34 | (declare-function ede-directory-safe-p "ede") |
35 | (declare-function ede-add-project-to-global-list "ede") | |
36 | ||
62a81506 CY |
37 | (defclass ede-project-autoload-dirmatch () |
38 | ((fromconfig :initarg :fromconfig | |
39 | :initform nil | |
40 | :documentation | |
41 | "A config file within which the match pattern lives.") | |
42 | (configregex :initarg :configregex | |
43 | :initform nil | |
44 | :documentation | |
45 | "A regexp to identify the dirmatch pattern.") | |
46 | (configregexidx :initarg :configregexidx | |
47 | :initform nil | |
48 | :documentation | |
49 | "An index into the match-data of `configregex'.") | |
50 | (configdatastash :initform nil | |
51 | :documentation | |
52 | "Save discovered match string.") | |
53 | ) | |
54 | "Support complex matches for projects that live in named directories. | |
55 | For most cases, a simple string is sufficient. If, however, a project | |
56 | location is varied dependent on other complex criteria, this class | |
57 | can be used to define that match without loading the specific project | |
58 | into memory.") | |
59 | ||
60 | (defmethod ede-dirmatch-installed ((dirmatch ede-project-autoload-dirmatch)) | |
61 | "Return non-nil if the tool DIRMATCH might match is installed on the system." | |
62 | (let ((fc (oref dirmatch fromconfig))) | |
63 | ||
64 | (cond | |
65 | ;; If the thing to match is stored in a config file. | |
66 | ((stringp fc) | |
67 | (file-exists-p fc)) | |
68 | ||
69 | ;; Add new types of dirmatches here. | |
70 | ||
735135f9 | 71 | ;; Error for weird stuff |
62a81506 CY |
72 | (t (error "Unknown dirmatch type."))))) |
73 | ||
74 | ||
75 | (defmethod ede-do-dirmatch ((dirmatch ede-project-autoload-dirmatch) file) | |
76 | "Does DIRMATCH match the filename FILE." | |
77 | (let ((fc (oref dirmatch fromconfig))) | |
78 | ||
79 | (cond | |
80 | ;; If the thing to match is stored in a config file. | |
81 | ((stringp fc) | |
82 | (when (file-exists-p fc) | |
83 | (let ((matchstring (oref dirmatch configdatastash))) | |
84 | (unless matchstring | |
85 | (save-current-buffer | |
86 | (let* ((buff (get-file-buffer fc)) | |
87 | (readbuff | |
88 | (let ((find-file-hook nil)) ;; Disable ede from recursing | |
89 | (find-file-noselect fc)))) | |
90 | (set-buffer readbuff) | |
91 | (save-excursion | |
92 | (goto-char (point-min)) | |
93 | (when (re-search-forward (oref dirmatch configregex) nil t) | |
94 | (setq matchstring | |
95 | (match-string (or (oref dirmatch configregexidx) 0))))) | |
96 | (if (not buff) (kill-buffer readbuff)))) | |
97 | ;; Save what we find in our cache. | |
98 | (oset dirmatch configdatastash matchstring)) | |
99 | ;; Match against our discovered string | |
100 | (and matchstring (string-match (regexp-quote matchstring) file)) | |
101 | ))) | |
102 | ||
103 | ;; Add new matches here | |
104 | ;; ((stringp somenewslot ...) | |
105 | ;; ) | |
106 | ||
107 | ;; Error if none others known | |
108 | (t | |
109 | (error "Unknown dirmatch object match style."))) | |
110 | )) | |
111 | ||
112 | (declare-function ede-directory-safe-p "ede") | |
113 | (declare-function ede-add-project-to-global-list "ede") | |
114 | ||
cb85c0d8 EL |
115 | (defclass ede-project-autoload () |
116 | ((name :initarg :name | |
117 | :documentation "Name of this project type") | |
118 | (file :initarg :file | |
119 | :documentation "The lisp file belonging to this class.") | |
120 | (proj-file :initarg :proj-file | |
121 | :documentation "Name of a project file of this type.") | |
62a81506 CY |
122 | (proj-root-dirmatch :initarg :proj-root-dirmatch |
123 | :initform "" | |
124 | :type (or string ede-project-autoload-dirmatch) | |
125 | :documentation | |
126 | "To avoid loading a project, check if the directory matches this. | |
127 | For projects that use directory name matches, a function would load that project. | |
128 | Specifying this matcher will allow EDE to check without loading the project.") | |
cb85c0d8 EL |
129 | (proj-root :initarg :proj-root |
130 | :type function | |
131 | :documentation "A function symbol to call for the project root. | |
132 | This function takes no arguments, and returns the current directories | |
133 | root, if available. Leave blank to use the EDE directory walking | |
134 | routine instead.") | |
135 | (initializers :initarg :initializers | |
136 | :initform nil | |
137 | :documentation | |
138 | "Initializers passed to the project object. | |
139 | These are used so there can be multiple types of projects | |
0b381c7e | 140 | associated with a single object class, based on the initializers used.") |
cb85c0d8 EL |
141 | (load-type :initarg :load-type |
142 | :documentation "Fn symbol used to load this project file.") | |
143 | (class-sym :initarg :class-sym | |
144 | :documentation "Symbol representing the project class to use.") | |
62a81506 CY |
145 | (generic-p :initform nil |
146 | :documentation | |
147 | "Generic projects are added to the project list at the end. | |
148 | The add routine will set this to non-nil so that future non-generic placement will | |
149 | be successful.") | |
cb85c0d8 EL |
150 | (new-p :initarg :new-p |
151 | :initform t | |
152 | :documentation | |
153 | "Non-nil if this is an option when a user creates a project.") | |
a62d5ee1 EL |
154 | (safe-p :initarg :safe-p |
155 | :initform t | |
156 | :documentation | |
157 | "Non-nil if the project load files are \"safe\". | |
158 | An unsafe project is one that loads project variables via Emacs | |
159 | Lisp code. A safe project is one that loads project variables by | |
160 | scanning files without loading Lisp code from them.") | |
cb85c0d8 EL |
161 | ) |
162 | "Class representing minimal knowledge set to run preliminary EDE functions. | |
163 | When more advanced functionality is needed from a project type, that projects | |
164 | type is required and the load function used.") | |
165 | ||
166 | (defvar ede-project-class-files | |
167 | (list | |
168 | (ede-project-autoload "edeproject-makefile" | |
169 | :name "Make" :file 'ede/proj | |
170 | :proj-file "Project.ede" | |
171 | :load-type 'ede-proj-load | |
a62d5ee1 EL |
172 | :class-sym 'ede-proj-project |
173 | :safe-p nil) | |
cb85c0d8 EL |
174 | (ede-project-autoload "edeproject-automake" |
175 | :name "Automake" :file 'ede/proj | |
176 | :proj-file "Project.ede" | |
177 | :initializers '(:makefile-type Makefile.am) | |
178 | :load-type 'ede-proj-load | |
a62d5ee1 EL |
179 | :class-sym 'ede-proj-project |
180 | :safe-p nil) | |
cb85c0d8 EL |
181 | (ede-project-autoload "automake" |
182 | :name "automake" :file 'ede/project-am | |
183 | :proj-file "Makefile.am" | |
184 | :load-type 'project-am-load | |
185 | :class-sym 'project-am-makefile | |
62a81506 CY |
186 | :new-p nil |
187 | :safe-p t) | |
188 | ) | |
cb85c0d8 EL |
189 | "List of vectors defining how to determine what type of projects exist.") |
190 | ||
a62d5ee1 EL |
191 | (put 'ede-project-class-files 'risky-local-variable t) |
192 | ||
62a81506 CY |
193 | (defun ede-add-project-autoload (projauto &optional flag) |
194 | "Add PROJAUTO, an EDE autoload definition to `ede-project-class-files'. | |
195 | Optional argument FLAG indicates how this autoload should be | |
196 | added. Possible values are: | |
197 | 'generic - A generic project type. Keep this at the very end. | |
198 | 'unique - A unique project type for a specific project. Keep at the very | |
199 | front of the list so more generic projects don't get priority." | |
200 | ;; First, can we identify PROJAUTO as already in the list? If so, replace. | |
201 | (let ((projlist ede-project-class-files) | |
202 | (projname (object-name-string projauto))) | |
203 | (while (and projlist (not (string= (object-name-string (car projlist)) projname))) | |
204 | (setq projlist (cdr projlist))) | |
205 | ||
206 | (if projlist | |
207 | ;; Stick the new one into the old slot. | |
208 | (setcar projlist projauto) | |
209 | ||
210 | ;; Else, see where to insert it. | |
211 | (cond ((and flag (eq flag 'unique)) | |
212 | ;; Unique items get stuck right onto the front. | |
213 | (setq ede-project-class-files | |
214 | (cons projauto ede-project-class-files))) | |
215 | ||
216 | ;; Generic Projects go at the very end of the list. | |
217 | ((and flag (eq flag 'generic)) | |
218 | (oset projauto generic-p t) | |
219 | (setq ede-project-class-files | |
220 | (append ede-project-class-files | |
221 | (list projauto)))) | |
222 | ||
223 | ;; Normal projects go at the end of the list, but | |
224 | ;; before the generic projects. | |
225 | (t | |
226 | (let ((prev nil) | |
227 | (next ede-project-class-files)) | |
228 | (while (and next (not (oref (car next) generic-p))) | |
229 | (setq prev next | |
230 | next (cdr next))) | |
231 | (when (not prev) | |
232 | (error "ede-project-class-files not initialized")) | |
233 | ;; Splice into the list. | |
234 | (setcdr prev (cons projauto next)))))))) | |
235 | ||
cb85c0d8 EL |
236 | ;;; EDE project-autoload methods |
237 | ;; | |
238 | (defmethod ede-project-root ((this ede-project-autoload)) | |
239 | "If a project knows its root, return it here. | |
240 | Allows for one-project-object-for-a-tree type systems." | |
241 | nil) | |
242 | ||
62a81506 CY |
243 | (defun ede-project-dirmatch-p (file dirmatch) |
244 | "Return non-nil if FILE matches DIRMATCH. | |
245 | DIRMATCH could be nil (no match), a string (regexp match), | |
246 | or an `ede-project-autoload-dirmatch' object." | |
247 | ;; If dirmatch is a string, then we simply match it against | |
248 | ;; the file we are testing. | |
249 | (if (stringp dirmatch) | |
250 | (string-match dirmatch file) | |
251 | ;; if dirmatch is instead a dirmatch object, we test against | |
252 | ;; that object instead. | |
253 | (if (ede-project-autoload-dirmatch-p dirmatch) | |
254 | (ede-do-dirmatch dirmatch file) | |
255 | (error "Unknown project directory match type.")) | |
256 | )) | |
257 | ||
cb85c0d8 EL |
258 | (defmethod ede-project-root-directory ((this ede-project-autoload) |
259 | &optional file) | |
260 | "If a project knows its root, return it here. | |
261 | Allows for one-project-object-for-a-tree type systems. | |
262 | Optional FILE is the file to test. If there is no FILE, use | |
263 | the current buffer." | |
264 | (when (not file) | |
265 | (setq file default-directory)) | |
266 | (when (slot-boundp this :proj-root) | |
62a81506 CY |
267 | (let ((dirmatch (oref this proj-root-dirmatch)) |
268 | (rootfcn (oref this proj-root)) | |
269 | (callfcn t)) | |
cb85c0d8 | 270 | (when rootfcn |
62a81506 CY |
271 | (if ;; If the dirmatch (an object) is not installed, then we |
272 | ;; always skip doing a match. | |
273 | (and (ede-project-autoload-dirmatch-p dirmatch) | |
274 | (not (ede-dirmatch-installed dirmatch))) | |
275 | (setq callfcn nil) | |
276 | ;; Other types of dirmatch: | |
277 | (when (and | |
278 | ;; If the Emacs Lisp file handling this project hasn't | |
279 | ;; been loaded, we will use the quick dirmatch feature. | |
280 | (not (featurep (oref this file))) | |
281 | ;; If the dirmatch is an empty string, then we always | |
282 | ;; skip doing a match. | |
283 | (not (and (stringp dirmatch) (string= dirmatch ""))) | |
284 | ) | |
285 | ;; If this file DOES NOT match dirmatch, we set the callfcn | |
286 | ;; to nil, meaning don't load the ede support file for this | |
287 | ;; type of project. If it does match, we will load the file | |
735135f9 | 288 | ;; and use a more accurate programmatic match from there. |
62a81506 CY |
289 | (unless (ede-project-dirmatch-p file dirmatch) |
290 | (setq callfcn nil)))) | |
291 | ;; Call into the project support file for a match. | |
292 | (when callfcn | |
293 | (condition-case nil | |
294 | (funcall rootfcn file) | |
295 | (error | |
296 | (funcall rootfcn)))) | |
cb85c0d8 EL |
297 | )))) |
298 | ||
299 | (defmethod ede-dir-to-projectfile ((this ede-project-autoload) dir) | |
300 | "Return a full file name of project THIS found in DIR. | |
301 | Return nil if the project file does not exist." | |
302 | (let* ((d (file-name-as-directory dir)) | |
303 | (root (ede-project-root-directory this d)) | |
304 | (pf (oref this proj-file)) | |
62a81506 | 305 | (dm (oref this proj-root-dirmatch)) |
cb85c0d8 EL |
306 | (f (cond ((stringp pf) |
307 | (expand-file-name pf (or root d))) | |
308 | ((and (symbolp pf) (fboundp pf)) | |
62a81506 CY |
309 | ;; If there is a symbol to call, lets make extra |
310 | ;; sure we really can call it without loading in | |
311 | ;; other EDE projects. This happens if the file is | |
312 | ;; already loaded, or if there is a dirmatch, but | |
313 | ;; root is empty. | |
314 | (when (and (featurep (oref this file)) | |
315 | (or (not (stringp dm)) | |
316 | (not (string= dm ""))) | |
317 | root) | |
318 | (funcall pf (or root d)))))) | |
cb85c0d8 EL |
319 | ) |
320 | (when (and f (file-exists-p f)) | |
321 | f))) | |
322 | ||
a62d5ee1 EL |
323 | (defmethod ede-auto-load-project ((this ede-project-autoload) dir) |
324 | "Load in the project associated with THIS project autoload description. | |
325 | THIS project description should be valid for DIR, where the project will | |
326 | be loaded." | |
327 | ;; Last line of defense: don't load unsafe projects. | |
328 | (when (not (or (oref this :safe-p) | |
329 | (ede-directory-safe-p dir))) | |
330 | (error "Attempt to load an unsafe project (bug elsewhere in EDE)")) | |
331 | ;; Things are good - so load the project. | |
332 | (let ((o (funcall (oref this load-type) dir))) | |
333 | (when (not o) | |
334 | (error "Project type error: :load-type failed to create a project")) | |
335 | (ede-add-project-to-global-list o))) | |
cb85c0d8 EL |
336 | |
337 | (provide 'ede/auto) | |
338 | ||
339 | ;;; ede/auto.el ends here |