Merge branch 'master' into comment-cache
[emacs.git] / lisp / cedet / ede / base.el
blob13d721a5f9afd5d81cde5a5a6828e1ab58751224
1 ;;; ede/base.el --- Baseclasses for EDE.
3 ;; Copyright (C) 2010-2017 Free Software Foundation, Inc.
5 ;; Author: Eric M. Ludlam <zappo@gnu.org>
7 ;; This file is part of GNU Emacs.
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.
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.
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/>.
22 ;;; Commentary:
24 ;; Baseclasses for EDE.
26 ;; Contains all the base structures needed by EDE.
28 ;;; Code:
29 (require 'eieio)
30 (require 'cl-generic)
31 (require 'eieio-speedbar)
32 (require 'ede/auto)
34 ;; Defined in ede.el:
35 (defvar ede-projects)
36 (defvar ede-object)
37 (defvar ede-object-root-project)
39 (declare-function data-debug-new-buffer "data-debug")
40 (declare-function data-debug-insert-object-slots "eieio-datadebug")
41 (declare-function ede-parent-project "ede" (&optional obj))
42 (declare-function ede-current-project "ede" (&optional dir))
44 ;;; TARGET
46 ;; The TARGET is an entity in a project that knows about files
47 ;; and features of those files.
49 (defclass ede-target (eieio-speedbar-directory-button)
50 ((buttonface :initform speedbar-file-face) ;override for superclass
51 (name :initarg :name
52 :type string
53 :custom string
54 :label "Name"
55 :group (default name)
56 :documentation "Name of this target.")
57 ;; @todo - I think this should be "dir", and not "path".
58 (path :initarg :path
59 :type string
60 ;:custom string
61 ;:label "Path to target"
62 ;:group (default name)
63 :documentation "The path to the sources of this target.
64 Relative to the path of the project it belongs to.")
65 (source :initarg :source
66 :initform nil
67 ;; I'd prefer a list of strings.
68 :type list
69 :custom (repeat (string :tag "File"))
70 :label "Source Files"
71 :group (default source)
72 :documentation "Source files in this target.")
73 (versionsource :initarg :versionsource
74 :initform nil
75 :type list
76 :custom (repeat (string :tag "File"))
77 :label "Source Files with Version String"
78 :group (source)
79 :documentation
80 "Source files with a version string in them.
81 These files are checked for a version string whenever the EDE version
82 of the master project is changed. When strings are found, the version
83 previously there is updated.")
84 ;; Class level slots
86 (sourcetype :allocation :class
87 :type list ;; list of symbols
88 :documentation
89 "A list of `ede-sourcecode' objects this class will handle.
90 This is used to match target objects with the compilers they can use, and
91 which files this object is interested in."
92 :accessor ede-object-sourcecode)
93 (keybindings :allocation :class
94 :initform (("D" . ede-debug-target))
95 :documentation
96 "Keybindings specialized to this type of target."
97 :accessor ede-object-keybindings)
98 (menu :allocation :class
99 :initform ( [ "Debug target" ede-debug-target
100 (ede-buffer-belongs-to-target-p) ]
101 [ "Run target" ede-run-target
102 (ede-buffer-belongs-to-target-p) ]
104 :documentation "Menu specialized to this type of target."
105 :accessor ede-object-menu)
107 "A target is a structure that describes a file set that produces something.
108 Targets, as with `Make', is an entity that will manage a file set
109 and knows how to compile or otherwise transform those files into some
110 other desired outcome.")
112 ;;; PROJECT/PLACEHOLDER
114 ;; Project placeholders are minimum parts of a project used
115 ;; by the project cache. The project cache can refer to these placeholders,
116 ;; and swap them out with the real-deal when that project is loaded.
118 (defclass ede-project-placeholder (eieio-speedbar-directory-button)
119 ((name :initarg :name
120 :initform "Untitled"
121 :type string
122 :custom string
123 :label "Name"
124 :group (default name)
125 :documentation "The name used when generating distribution files.")
126 (version :initarg :version
127 :initform "1.0"
128 :type string
129 :custom string
130 :label "Version"
131 :group (default name)
132 :documentation "The version number used when distributing files.")
133 (directory :type string
134 :initarg :directory
135 :documentation "Directory this project is associated with.")
136 (dirinode :documentation "The inode id for :directory.")
137 (file :type string
138 :initarg :file
139 :documentation "The File uniquely tagging this project instance.
140 For some project types, this will be the file that stores the project configuration.
141 In other projects types, this file is merely a unique identifier to this type of project.")
142 (rootproject ; :initarg - no initarg, don't save this slot!
143 :initform nil
144 :type (or null ede-project-placeholder-child)
145 :documentation "Pointer to our root project.")
147 "Placeholder object for projects not loaded into memory.
148 Projects placeholders will be stored in a user specific location
149 and querying them will cause the actual project to get loaded.")
151 ;;; PROJECT
153 ;; An EDE project controls a set of TARGETS, and can also contain
154 ;; multiple SUBPROJECTS.
156 ;; The project defines a set of features that need to be built from
157 ;; files, in addition as to controlling what to do with the file set,
158 ;; such as creating distributions, compilation, and web sites.
160 ;; Projects can also affect how EDE works, by changing what appears in
161 ;; the EDE menu, or how some keys are bound.
163 (unless (fboundp 'ede-target-list-p)
164 (cl-deftype ede-target-list () '(list-of ede-target)))
166 (defclass ede-project (ede-project-placeholder)
167 ((subproj :initform nil
168 :type list
169 :documentation "Sub projects controlled by this project.
170 For Automake based projects, each directory is treated as a project.")
171 (targets :initarg :targets
172 :type ede-target-list
173 :custom (repeat (object :objectcreatefcn ede-new-target-custom))
174 :label "Local Targets"
175 :group (targets)
176 :documentation "List of top level targets in this project.")
177 (locate-obj :type (or null ede-locate-base-child)
178 :documentation
179 "A locate object to use as a backup to `ede-expand-filename'.")
180 (tool-cache :initarg :tool-cache
181 :type list
182 :custom (repeat object)
183 :label "Tool: "
184 :group tools
185 :documentation "List of tool cache configurations in this project.
186 This allows any tool to create, manage, and persist project-specific settings.")
187 (mailinglist :initarg :mailinglist
188 :initform ""
189 :type string
190 :custom string
191 :label "Mailing List Address"
192 :group name
193 :documentation
194 "An email address where users might send email for help.")
195 (web-site-url :initarg :web-site-url
196 :initform ""
197 :type string
198 :custom string
199 :label "Web Site URL"
200 :group name
201 :documentation "URL to this projects web site.
202 This is a URL to be sent to a web site for documentation.")
203 (web-site-directory :initarg :web-site-directory
204 :initform ""
205 :custom string
206 :label "Web Page Directory"
207 :group name
208 :documentation
209 "A directory where web pages can be found by Emacs.
210 For remote locations use a path compatible with ange-ftp or EFS.
211 You can also use TRAMP for use with rcp & scp.")
212 (web-site-file :initarg :web-site-file
213 :initform ""
214 :custom string
215 :label "Web Page File"
216 :group name
217 :documentation
218 "A file which contains the home page for this project.
219 This file can be relative to slot `web-site-directory'.
220 This can be a local file, use ange-ftp, EFS, or TRAMP.")
221 (ftp-site :initarg :ftp-site
222 :initform ""
223 :type string
224 :custom string
225 :label "FTP site"
226 :group name
227 :documentation
228 "FTP site where this project's distribution can be found.
229 This FTP site should be in Emacs form, as needed by `ange-ftp', but can
230 also be of a form used by TRAMP for use with scp, or rcp.")
231 (ftp-upload-site :initarg :ftp-upload-site
232 :initform ""
233 :type string
234 :custom string
235 :label "FTP Upload site"
236 :group name
237 :documentation
238 "FTP Site to upload new distributions to.
239 This FTP site should be in Emacs form as needed by `ange-ftp'.
240 If this slot is nil, then use `ftp-site' instead.")
241 (configurations :initarg :configurations
242 :initform ("debug" "release")
243 :type list
244 :custom (repeat string)
245 :label "Configuration Options"
246 :group (settings)
247 :documentation "List of available configuration types.
248 Individual target/project types can form associations between a configuration,
249 and target specific elements such as build variables.")
250 (configuration-default :initarg :configuration-default
251 :initform "debug"
252 :custom string
253 :label "Current Configuration"
254 :group (settings)
255 :documentation "The default configuration.")
256 (local-variables :initarg :local-variables
257 :initform nil
258 :custom (repeat (cons (sexp :tag "Variable")
259 (sexp :tag "Value")))
260 :label "Project Local Variables"
261 :group (settings)
262 :documentation "Project local variables")
263 (keybindings :allocation :class
264 :initform (("D" . ede-debug-target)
265 ("R" . ede-run-target))
266 :documentation "Keybindings specialized to this type of target."
267 :accessor ede-object-keybindings)
268 (menu :allocation :class
269 :initform
271 [ "Update Version" ede-update-version ede-object ]
272 [ "Version Control Status" ede-vc-project-directory ede-object ]
273 [ "Edit Project Homepage" ede-edit-web-page
274 (and ede-object (oref (ede-toplevel) web-site-file)) ]
275 [ "Browse Project URL" ede-web-browse-home
276 (and ede-object
277 (not (string= "" (oref (ede-toplevel) web-site-url)))) ]
278 "--"
279 [ "Rescan Project Files" ede-rescan-toplevel t ]
280 [ "Edit Projectfile" ede-edit-file-target
281 (ede-buffer-belongs-to-project-p) ]
283 :documentation "Menu specialized to this type of target."
284 :accessor ede-object-menu)
286 "Top level EDE project specification.
287 All specific project types must derive from this project."
288 :method-invocation-order :depth-first)
290 ;;; Important macros for doing commands.
292 (defmacro ede-with-projectfile (obj &rest forms)
293 "For the project in which OBJ resides, execute FORMS."
294 (declare (indent 1))
295 (unless (symbolp obj)
296 (message "Beware! ede-with-projectfile's first arg is copied: %S" obj))
297 `(let* ((pf (if (obj-of-class-p ,obj 'ede-target)
298 (ede-target-parent ,obj)
299 ,obj))
300 (dbka (get-file-buffer (oref pf file))))
301 (with-current-buffer
302 (if (not dbka) (find-file-noselect (oref pf file))
303 dbka)
304 ,@forms
305 (if (not dbka) (kill-buffer (current-buffer))))))
307 ;;; The EDE persistent cache.
309 ;; The cache is a way to mark where all known projects live without
310 ;; loading those projects into memory, or scanning for them each time
311 ;; emacs starts.
313 (defcustom ede-project-placeholder-cache-file
314 (locate-user-emacs-file "ede-projects.el" ".projects.ede")
315 "File containing the list of projects EDE has viewed.
316 If set to nil, then the cache is not saved."
317 :group 'ede
318 :type 'file)
320 (defvar ede-project-cache-files nil
321 "List of project files EDE has seen before.")
323 (defun ede-save-cache ()
324 "Save a cache of EDE objects that Emacs has seen before."
325 (interactive)
326 (when ede-project-placeholder-cache-file
327 (let ((p ede-projects)
328 (c ede-project-cache-files)
329 (recentf-exclude '( (lambda (f) t) ))
331 (condition-case nil
332 (progn
333 (set-buffer (find-file-noselect ede-project-placeholder-cache-file t))
334 (erase-buffer)
335 (insert ";; EDE project cache file.
336 ;; This contains a list of projects you have visited.\n(")
337 (while p
338 (when (and (car p) (ede-project-p p))
339 (let ((f (oref (car p) file)))
340 (when (file-exists-p f)
341 (insert "\n \"" f "\""))))
342 (setq p (cdr p)))
343 (while c
344 (insert "\n \"" (car c) "\"")
345 (setq c (cdr c)))
346 (insert "\n)\n")
347 (condition-case nil
348 (save-buffer 0)
349 (error
350 (message "File %s could not be saved."
351 ede-project-placeholder-cache-file)))
352 (kill-buffer (current-buffer))
354 (error
355 (message "File %s could not be read."
356 ede-project-placeholder-cache-file))
358 ))))
360 (defun ede-load-cache ()
361 "Load the cache of EDE projects."
362 (save-excursion
363 (let ((cachebuffer (get-buffer-create "*ede cache*")))
364 (condition-case nil
365 (with-current-buffer cachebuffer
366 (erase-buffer)
367 (when (file-exists-p ede-project-placeholder-cache-file)
368 (insert-file-contents ede-project-placeholder-cache-file))
369 (goto-char (point-min))
370 (let ((c (read (current-buffer)))
371 (new nil)
372 (p ede-projects))
373 ;; Remove loaded projects from the cache.
374 (while p
375 (setq c (delete (oref (car p) file) c))
376 (setq p (cdr p)))
377 ;; Remove projects that aren't on the filesystem
378 ;; anymore.
379 (while c
380 (when (file-exists-p (car c))
381 (setq new (cons (car c) new)))
382 (setq c (cdr c)))
383 ;; Save it
384 (setq ede-project-cache-files (nreverse new))))
385 (error nil))
386 (when cachebuffer (kill-buffer cachebuffer))
389 ;;; Get the cache usable.
391 ;; @TODO - Remove this cache setup, or use this for something helpful.
392 ;;(add-hook 'kill-emacs-hook 'ede-save-cache)
393 ;;(when (not noninteractive)
394 ;; ;; No need to load the EDE cache if we aren't interactive.
395 ;; ;; This occurs during batch byte-compiling of other tools.
396 ;; (ede-load-cache))
399 ;;; METHODS
401 ;; The methods in ede-base handle project related behavior, and DO NOT
402 ;; related to EDE mode commands directory, such as keybindings.
404 ;; Mode related methods are in ede.el. These methods are related
405 ;; project specific activities not directly tied to a keybinding.
406 (cl-defmethod ede-subproject-relative-path ((proj ede-project) &optional parent-in)
407 "Get a path name for PROJ which is relative to the parent project.
408 If PARENT is specified, then be relative to the PARENT project.
409 Specifying PARENT is useful for sub-sub projects relative to the root project."
410 (let* ((parent (or parent-in (ede-parent-project proj)))
411 (dir (file-name-directory (oref proj file))))
412 (if (and parent (not (eq parent proj)))
413 (file-relative-name dir (file-name-directory (oref parent file)))
414 "")))
416 (cl-defmethod ede-subproject-p ((proj ede-project))
417 "Return non-nil if PROJ is a sub project."
418 ;; @TODO - Use this in more places, and also pay attention to
419 ;; metasubproject in ede/proj.el
420 (ede-parent-project proj))
423 ;;; Default descriptive methods for EDE classes
425 ;; These are methods which you might want to override, but there is
426 ;; no need to in most situations because they are either a) simple, or
427 ;; b) cosmetic.
429 (cl-defmethod ede-name ((this ede-target))
430 "Return the name of THIS target."
431 (oref this name))
433 (cl-defmethod ede-target-name ((this ede-target))
434 "Return the name of THIS target, suitable for make or debug style commands."
435 (oref this name))
437 (cl-defmethod ede-name ((this ede-project))
438 "Return a short-name for THIS project file.
439 Do this by extracting the lowest directory name."
440 (oref this name))
442 (cl-defmethod ede-description ((this ede-project))
443 "Return a description suitable for the minibuffer about THIS."
444 (format "Project %s: %d subprojects, %d targets."
445 (ede-name this) (length (oref this subproj))
446 (length (oref this targets))))
448 (cl-defmethod ede-description ((this ede-target))
449 "Return a description suitable for the minibuffer about THIS."
450 (format "Target %s: with %d source files."
451 (ede-name this) (length (oref this source))))
453 ;;; HEADERS/DOC
455 ;; Targets and projects are often associated with other files, such as
456 ;; header files, documentation files and the like. Have strong
457 ;; associations can make useful user commands to quickly navigate
458 ;; between the files based on their associations.
460 (defun ede-header-file ()
461 "Return the header file for the current buffer.
462 Not all buffers need headers, so return nil if no applicable."
463 (if ede-object
464 (ede-buffer-header-file ede-object (current-buffer))
465 nil))
467 (cl-defmethod ede-buffer-header-file ((this ede-project) buffer)
468 "Return nil, projects don't have header files."
469 nil)
471 (cl-defmethod ede-buffer-header-file ((this ede-target) buffer)
472 "There are no default header files in EDE.
473 Do a quick check to see if there is a Header tag in this buffer."
474 (with-current-buffer buffer
475 (if (re-search-forward "::Header:: \\([a-zA-Z0-9.]+\\)" nil t)
476 (buffer-substring-no-properties (match-beginning 1)
477 (match-end 1))
478 (let ((src (ede-target-sourcecode this))
479 (found nil))
480 (while (and src (not found))
481 (setq found (ede-buffer-header-file (car src) (buffer-file-name))
482 src (cdr src)))
483 found))))
485 (defun ede-documentation-files ()
486 "Return the documentation files for the current buffer.
487 Not all buffers need documentations, so return nil if no applicable.
488 Some projects may have multiple documentation files, so return a list."
489 (if ede-object
490 (ede-buffer-documentation-files ede-object (current-buffer))
491 nil))
493 (cl-defmethod ede-buffer-documentation-files ((this ede-project) buffer)
494 "Return all documentation in project THIS based on BUFFER."
495 ;; Find the info node.
496 (ede-documentation this))
498 (cl-defmethod ede-buffer-documentation-files ((this ede-target) buffer)
499 "Check for some documentation files for THIS.
500 Also do a quick check to see if there is a Documentation tag in this BUFFER."
501 (with-current-buffer buffer
502 (if (re-search-forward "::Documentation:: \\([a-zA-Z0-9.]+\\)" nil t)
503 (buffer-substring-no-properties (match-beginning 1)
504 (match-end 1))
505 ;; Check the master project
506 (let ((cp (ede-toplevel)))
507 (ede-buffer-documentation-files cp (current-buffer))))))
509 (cl-defmethod ede-documentation ((this ede-project))
510 "Return a list of files that provide documentation.
511 Documentation is not for object THIS, but is provided by THIS for other
512 files in the project."
513 (let ((targ (oref this targets))
514 (proj (oref this subproj))
515 (found nil))
516 (while targ
517 (setq found (append (ede-documentation (car targ)) found)
518 targ (cdr targ)))
519 (while proj
520 (setq found (append (ede-documentation (car proj)) found)
521 proj (cdr proj)))
522 found))
524 (cl-defmethod ede-documentation ((this ede-target))
525 "Return a list of files that provide documentation.
526 Documentation is not for object THIS, but is provided by THIS for other
527 files in the project."
528 nil)
530 (defun ede-html-documentation-files ()
531 "Return a list of HTML documentation files associated with this project."
532 (ede-html-documentation (ede-toplevel))
535 (cl-defmethod ede-html-documentation ((this ede-project))
536 "Return a list of HTML files provided by project THIS."
540 ;;; Default "WANT" methods.
542 ;; These methods are used to determine if a target "wants", or could
543 ;; somehow handle a file, or some source type.
545 (cl-defmethod ede-want-file-p ((this ede-target) file)
546 "Return non-nil if THIS target wants FILE."
547 ;; By default, all targets reference the source object, and let it decide.
548 (let ((src (ede-target-sourcecode this)))
549 (while (and src (not (ede-want-file-p (car src) file)))
550 (setq src (cdr src)))
551 src))
553 (cl-defmethod ede-want-file-source-p ((this ede-target) file)
554 "Return non-nil if THIS target wants FILE."
555 ;; By default, all targets reference the source object, and let it decide.
556 (let ((src (ede-target-sourcecode this)))
557 (while (and src (not (ede-want-file-source-p (car src) file)))
558 (setq src (cdr src)))
559 src))
561 (cl-defmethod ede-target-sourcecode ((this ede-target))
562 "Return the sourcecode objects which THIS permits."
563 (let ((sc (oref this sourcetype))
564 (rs nil))
565 (while (and (listp sc) sc)
566 (setq rs (cons (symbol-value (car sc)) rs)
567 sc (cdr sc)))
568 rs))
571 ;;; Debugging.
573 (defun ede-adebug-project ()
574 "Run adebug against the current EDE project.
575 Display the results as a debug list."
576 (interactive)
577 (require 'data-debug)
578 (when (ede-current-project)
579 (data-debug-new-buffer "*Analyzer ADEBUG*")
580 (data-debug-insert-object-slots (ede-current-project) "")
583 (defun ede-adebug-project-parent ()
584 "Run adebug against the current EDE parent project.
585 Display the results as a debug list."
586 (interactive)
587 (require 'data-debug)
588 (when (ede-parent-project)
589 (data-debug-new-buffer "*Analyzer ADEBUG*")
590 (data-debug-insert-object-slots (ede-parent-project) "")
593 (defun ede-adebug-project-root ()
594 "Run adebug against the current EDE parent project.
595 Display the results as a debug list."
596 (interactive)
597 (require 'data-debug)
598 (when (ede-toplevel)
599 (data-debug-new-buffer "*Analyzer ADEBUG*")
600 (data-debug-insert-object-slots (ede-toplevel) "")
605 ;;; TOPLEVEL PROJECT
607 ;; The toplevel project is a way to identify the EDE structure that belongs
608 ;; to the top of a project.
610 (defun ede-toplevel (&optional subproj)
611 "Return the ede project which is the root of the current project.
612 Optional argument SUBPROJ indicates a subproject to start from
613 instead of the current project."
614 (or (when (not subproj) ede-object-root-project)
615 (let* ((cp (or subproj (ede-current-project))))
616 (or (and cp (ede-project-root cp))
617 (progn
618 (while (ede-parent-project cp)
619 (setq cp (ede-parent-project cp)))
620 cp)))))
623 ;;; Utility functions
626 (defun ede-normalize-file/directory (this project-file-name)
627 "Fills :directory or :file slots if they're missing in project THIS.
628 The other slot will be used to calculate values.
629 PROJECT-FILE-NAME is a name of project file (short name, like `pom.xml', etc."
630 (when (and (or (not (slot-boundp this :file))
631 (not (oref this :file)))
632 (slot-boundp this :directory)
633 (oref this :directory))
634 (oset this :file (expand-file-name project-file-name (oref this :directory))))
635 (when (and (or (not (slot-boundp this :directory))
636 (not (oref this :directory)))
637 (slot-boundp this :file)
638 (oref this :file))
639 (oset this :directory (file-name-directory (oref this :file))))
645 ;;; Hooks & Autoloads
647 ;; These let us watch various activities, and respond appropriately.
649 ;; (add-hook 'edebug-setup-hook
650 ;; (lambda ()
651 ;; (def-edebug-spec ede-with-projectfile
652 ;; (form def-body))))
654 (provide 'ede/base)
656 ;; Local variables:
657 ;; generated-autoload-file: "loaddefs.el"
658 ;; generated-autoload-load-name: "ede/base"
659 ;; End:
661 ;;; ede/base.el ends here