1 ;;; ede/base.el --- Baseclasses for EDE.
3 ;; Copyright (C) 2010-2012 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/>.
24 ;; Baseclasses for EDE.
26 ;; Contains all the base structures needed by EDE.
30 (require 'eieio-speedbar
)
36 (defvar ede-object-root-project
)
38 (declare-function data-debug-new-buffer
"data-debug")
39 (declare-function data-debug-insert-object-slots
"eieio-datadebug")
40 (declare-function ede-parent-project
"ede" (&optional obj
))
41 (declare-function ede-current-project
"ede" (&optional dir
))
45 ;; The TARGET is an entity in a project that knows about files
46 ;; and features of those files.
48 (defclass ede-target
(eieio-speedbar-directory-button)
49 ((buttonface :initform speedbar-file-face
) ;override for superclass
55 :documentation
"Name of this target.")
56 ;; @todo - I think this should be "dir", and not "path".
60 ;:label "Path to target"
61 ;:group (default name)
62 :documentation
"The path to the sources of this target.
63 Relative to the path of the project it belongs to.")
64 (source :initarg
:source
66 ;; I'd prefer a list of strings.
68 :custom
(repeat (string :tag
"File"))
70 :group
(default source
)
71 :documentation
"Source files in this target.")
72 (versionsource :initarg
:versionsource
75 :custom
(repeat (string :tag
"File"))
76 :label
"Source Files with Version String"
79 "Source files with a version string in them.
80 These files are checked for a version string whenever the EDE version
81 of the master project is changed. When strings are found, the version
82 previously there is updated.")
85 (sourcetype :allocation
:class
86 :type list
;; list of symbols
88 "A list of `ede-sourcecode' objects this class will handle.
89 This is used to match target objects with the compilers they can use, and
90 which files this object is interested in."
91 :accessor ede-object-sourcecode
)
92 (keybindings :allocation
:class
93 :initform
(("D" . ede-debug-target
))
95 "Keybindings specialized to this type of target."
96 :accessor ede-object-keybindings
)
97 (menu :allocation
:class
98 :initform
( [ "Debug target" ede-debug-target
99 (ede-buffer-belongs-to-target-p) ]
100 [ "Run target" ede-run-target
101 (ede-buffer-belongs-to-target-p) ]
103 :documentation
"Menu specialized to this type of target."
104 :accessor ede-object-menu
)
106 "A target is a structure that describes a file set that produces something.
107 Targets, as with 'Make', is an entity that will manage a file set
108 and knows how to compile or otherwise transform those files into some
109 other desired outcome.")
111 ;;; PROJECT/PLACEHOLDER
113 ;; Project placeholders are minimum parts of a project used
114 ;; by the project cache. The project cache can refer to these placeholders,
115 ;; and swap them out with the real-deal when that project is loaded.
117 (defclass ede-project-placeholder
(eieio-speedbar-directory-button)
118 ((name :initarg
:name
123 :group
(default name
)
124 :documentation
"The name used when generating distribution files.")
125 (version :initarg
:version
130 :group
(default name
)
131 :documentation
"The version number used when distributing files.")
132 (directory :type string
134 :documentation
"Directory this project is associated with.")
135 (dirinode :documentation
"The inode id for :directory.")
138 :documentation
"File name where this project is stored.")
139 (rootproject ; :initarg - no initarg, don't save this slot!
141 :type
(or null ede-project-placeholder-child
)
142 :documentation
"Pointer to our root project.")
144 "Placeholder object for projects not loaded into memory.
145 Projects placeholders will be stored in a user specific location
146 and querying them will cause the actual project to get loaded.")
150 ;; An EDE project controls a set of TARGETS, and can also contain
151 ;; multiple SUBPROJECTS.
153 ;; The project defines a set of features that need to be built from
154 ;; files, in addition as to controlling what to do with the file set,
155 ;; such as creating distributions, compilation, and web sites.
157 ;; Projects can also affect how EDE works, by changing what appears in
158 ;; the EDE menu, or how some keys are bound.
160 (defclass ede-project
(ede-project-placeholder)
161 ((subproj :initform nil
163 :documentation
"Sub projects controlled by this project.
164 For Automake based projects, each directory is treated as a project.")
165 (targets :initarg
:targets
166 :type ede-target-list
167 :custom
(repeat (object :objectcreatefcn ede-new-target-custom
))
168 :label
"Local Targets"
170 :documentation
"List of top level targets in this project.")
171 (locate-obj :type
(or null ede-locate-base-child
)
173 "A locate object to use as a backup to `ede-expand-filename'.")
174 (tool-cache :initarg
:tool-cache
176 :custom
(repeat object
)
179 :documentation
"List of tool cache configurations in this project.
180 This allows any tool to create, manage, and persist project-specific settings.")
181 (mailinglist :initarg
:mailinglist
185 :label
"Mailing List Address"
188 "An email address where users might send email for help.")
189 (web-site-url :initarg
:web-site-url
193 :label
"Web Site URL"
195 :documentation
"URL to this projects web site.
196 This is a URL to be sent to a web site for documentation.")
197 (web-site-directory :initarg
:web-site-directory
200 :label
"Web Page Directory"
203 "A directory where web pages can be found by Emacs.
204 For remote locations use a path compatible with ange-ftp or EFS.
205 You can also use TRAMP for use with rcp & scp.")
206 (web-site-file :initarg
:web-site-file
209 :label
"Web Page File"
212 "A file which contains the home page for this project.
213 This file can be relative to slot `web-site-directory'.
214 This can be a local file, use ange-ftp, EFS, or TRAMP.")
215 (ftp-site :initarg
:ftp-site
222 "FTP site where this project's distribution can be found.
223 This FTP site should be in Emacs form, as needed by `ange-ftp', but can
224 also be of a form used by TRAMP for use with scp, or rcp.")
225 (ftp-upload-site :initarg
:ftp-upload-site
229 :label
"FTP Upload site"
232 "FTP Site to upload new distributions to.
233 This FTP site should be in Emacs form as needed by `ange-ftp'.
234 If this slot is nil, then use `ftp-site' instead.")
235 (configurations :initarg
:configurations
236 :initform
("debug" "release")
238 :custom
(repeat string
)
239 :label
"Configuration Options"
241 :documentation
"List of available configuration types.
242 Individual target/project types can form associations between a configuration,
243 and target specific elements such as build variables.")
244 (configuration-default :initarg
:configuration-default
247 :label
"Current Configuration"
249 :documentation
"The default configuration.")
250 (local-variables :initarg
:local-variables
252 :custom
(repeat (cons (sexp :tag
"Variable")
253 (sexp :tag
"Value")))
254 :label
"Project Local Variables"
256 :documentation
"Project local variables")
257 (keybindings :allocation
:class
258 :initform
(("D" . ede-debug-target
)
259 ("R" . ede-run-target
))
260 :documentation
"Keybindings specialized to this type of target."
261 :accessor ede-object-keybindings
)
262 (menu :allocation
:class
265 [ "Update Version" ede-update-version ede-object
]
266 [ "Version Control Status" ede-vc-project-directory ede-object
]
267 [ "Edit Project Homepage" ede-edit-web-page
268 (and ede-object
(oref (ede-toplevel) web-site-file
)) ]
269 [ "Browse Project URL" ede-web-browse-home
271 (not (string= "" (oref (ede-toplevel) web-site-url
)))) ]
273 [ "Rescan Project Files" ede-rescan-toplevel t
]
274 [ "Edit Projectfile" ede-edit-file-target
275 (ede-buffer-belongs-to-project-p) ]
277 :documentation
"Menu specialized to this type of target."
278 :accessor ede-object-menu
)
280 "Top level EDE project specification.
281 All specific project types must derive from this project."
282 :method-invocation-order
:depth-first
)
284 ;;; Important macros for doing commands.
286 (defmacro ede-with-projectfile
(obj &rest forms
)
287 "For the project in which OBJ resides, execute FORMS."
288 `(save-window-excursion
289 (let* ((pf (if (obj-of-class-p ,obj ede-target
)
290 (ede-target-parent ,obj
)
292 (dbka (get-file-buffer (oref pf file
))))
293 (if (not dbka
) (find-file (oref pf file
))
294 (switch-to-buffer dbka
))
296 (if (not dbka
) (kill-buffer (current-buffer))))))
297 (put 'ede-with-projectfile
'lisp-indent-function
1)
299 ;;; The EDE persistent cache.
301 ;; The cache is a way to mark where all known projects live without
302 ;; loading those projects into memory, or scanning for them each time
305 (defcustom ede-project-placeholder-cache-file
306 (locate-user-emacs-file "ede-projects.el" ".projects.ede")
307 "File containing the list of projects EDE has viewed."
311 (defvar ede-project-cache-files nil
312 "List of project files EDE has seen before.")
314 (defun ede-save-cache ()
315 "Save a cache of EDE objects that Emacs has seen before."
317 (let ((p ede-projects
)
318 (c ede-project-cache-files
)
319 (recentf-exclude '( (lambda (f) t
) ))
323 (set-buffer (find-file-noselect ede-project-placeholder-cache-file t
))
325 (insert ";; EDE project cache file.
326 ;; This contains a list of projects you have visited.\n(")
328 (when (and (car p
) (ede-project-p p
))
329 (let ((f (oref (car p
) file
)))
330 (when (file-exists-p f
)
331 (insert "\n \"" f
"\""))))
334 (insert "\n \"" (car c
) "\"")
340 (message "File %s could not be saved."
341 ede-project-placeholder-cache-file
)))
342 (kill-buffer (current-buffer))
345 (message "File %s could not be read."
346 ede-project-placeholder-cache-file
))
350 (defun ede-load-cache ()
351 "Load the cache of EDE projects."
353 (let ((cachebuffer nil
))
357 (find-file-noselect ede-project-placeholder-cache-file t
))
358 (set-buffer cachebuffer
)
359 (goto-char (point-min))
360 (let ((c (read (current-buffer)))
363 ;; Remove loaded projects from the cache.
365 (setq c
(delete (oref (car p
) file
) c
))
367 ;; Remove projects that aren't on the filesystem
370 (when (file-exists-p (car c
))
371 (setq new
(cons (car c
) new
)))
374 (setq ede-project-cache-files
(nreverse new
))))
376 (when cachebuffer
(kill-buffer cachebuffer
))
379 ;;; Get the cache usable.
381 ;; @TODO - Remove this cache setup, or use this for something helpful.
382 ;;(add-hook 'kill-emacs-hook 'ede-save-cache)
383 ;;(when (not noninteractive)
384 ;; ;; No need to load the EDE cache if we aren't interactive.
385 ;; ;; This occurs during batch byte-compiling of other tools.
391 ;; The methods in ede-base handle project related behavior, and DO NOT
392 ;; related to EDE mode commands directory, such as keybindings.
394 ;; Mode related methods are in ede.el. These methods are related
395 ;; project specific activities not directly tied to a keybinding.
396 (defmethod ede-subproject-relative-path ((proj ede-project
) &optional parent-in
)
397 "Get a path name for PROJ which is relative to the parent project.
398 If PARENT is specified, then be relative to the PARENT project.
399 Specifying PARENT is useful for sub-sub projects relative to the root project."
400 (let* ((parent (or parent-in
(ede-parent-project proj
)))
401 (dir (file-name-directory (oref proj file
))))
402 (if (and parent
(not (eq parent proj
)))
403 (file-relative-name dir
(file-name-directory (oref parent file
)))
406 (defmethod ede-subproject-p ((proj ede-project
))
407 "Return non-nil if PROJ is a sub project."
408 ;; @TODO - Use this in more places, and also pay attention to
409 ;; metasubproject in ede/proj.el
410 (ede-parent-project proj
))
413 ;;; Default descriptive methods for EDE classes
415 ;; These are methods which you might want to override, but there is
416 ;; no need to in most situations because they are either a) simple, or
419 (defmethod ede-name ((this ede-target
))
420 "Return the name of THIS target."
423 (defmethod ede-target-name ((this ede-target
))
424 "Return the name of THIS target, suitable for make or debug style commands."
427 (defmethod ede-name ((this ede-project
))
428 "Return a short-name for THIS project file.
429 Do this by extracting the lowest directory name."
432 (defmethod ede-description ((this ede-project
))
433 "Return a description suitable for the minibuffer about THIS."
434 (format "Project %s: %d subprojects, %d targets."
435 (ede-name this
) (length (oref this subproj
))
436 (length (oref this targets
))))
438 (defmethod ede-description ((this ede-target
))
439 "Return a description suitable for the minibuffer about THIS."
440 (format "Target %s: with %d source files."
441 (ede-name this
) (length (oref this source
))))
445 ;; Targets and projects are often associated with other files, such as
446 ;; header files, documentation files and the like. Have strong
447 ;; associations can make useful user commands to quickly navigate
448 ;; between the files based on their associations.
450 (defun ede-header-file ()
451 "Return the header file for the current buffer.
452 Not all buffers need headers, so return nil if no applicable."
454 (ede-buffer-header-file ede-object
(current-buffer))
457 (defmethod ede-buffer-header-file ((this ede-project
) buffer
)
458 "Return nil, projects don't have header files."
461 (defmethod ede-buffer-header-file ((this ede-target
) buffer
)
462 "There are no default header files in EDE.
463 Do a quick check to see if there is a Header tag in this buffer."
464 (with-current-buffer buffer
465 (if (re-search-forward "::Header:: \\([a-zA-Z0-9.]+\\)" nil t
)
466 (buffer-substring-no-properties (match-beginning 1)
468 (let ((src (ede-target-sourcecode this
))
470 (while (and src
(not found
))
471 (setq found
(ede-buffer-header-file (car src
) (buffer-file-name))
475 (defun ede-documentation-files ()
476 "Return the documentation files for the current buffer.
477 Not all buffers need documentations, so return nil if no applicable.
478 Some projects may have multiple documentation files, so return a list."
480 (ede-buffer-documentation-files ede-object
(current-buffer))
483 (defmethod ede-buffer-documentation-files ((this ede-project
) buffer
)
484 "Return all documentation in project THIS based on BUFFER."
485 ;; Find the info node.
486 (ede-documentation this
))
488 (defmethod ede-buffer-documentation-files ((this ede-target
) buffer
)
489 "Check for some documentation files for THIS.
490 Also do a quick check to see if there is a Documentation tag in this BUFFER."
491 (with-current-buffer buffer
492 (if (re-search-forward "::Documentation:: \\([a-zA-Z0-9.]+\\)" nil t
)
493 (buffer-substring-no-properties (match-beginning 1)
495 ;; Check the master project
496 (let ((cp (ede-toplevel)))
497 (ede-buffer-documentation-files cp
(current-buffer))))))
499 (defmethod ede-documentation ((this ede-project
))
500 "Return a list of files that provide documentation.
501 Documentation is not for object THIS, but is provided by THIS for other
502 files in the project."
503 (let ((targ (oref this targets
))
504 (proj (oref this subproj
))
507 (setq found
(append (ede-documentation (car targ
)) found
)
510 (setq found
(append (ede-documentation (car proj
)) found
)
514 (defmethod ede-documentation ((this ede-target
))
515 "Return a list of files that provide documentation.
516 Documentation is not for object THIS, but is provided by THIS for other
517 files in the project."
520 (defun ede-html-documentation-files ()
521 "Return a list of HTML documentation files associated with this project."
522 (ede-html-documentation (ede-toplevel))
525 (defmethod ede-html-documentation ((this ede-project
))
526 "Return a list of HTML files provided by project THIS."
530 ;;; Default "WANT" methods.
532 ;; These methods are used to determine if a target "wants", or could
533 ;; somehow handle a file, or some source type.
535 (defmethod ede-want-file-p ((this ede-target
) file
)
536 "Return non-nil if THIS target wants FILE."
537 ;; By default, all targets reference the source object, and let it decide.
538 (let ((src (ede-target-sourcecode this
)))
539 (while (and src
(not (ede-want-file-p (car src
) file
)))
540 (setq src
(cdr src
)))
543 (defmethod ede-want-file-source-p ((this ede-target
) file
)
544 "Return non-nil if THIS target wants FILE."
545 ;; By default, all targets reference the source object, and let it decide.
546 (let ((src (ede-target-sourcecode this
)))
547 (while (and src
(not (ede-want-file-source-p (car src
) file
)))
548 (setq src
(cdr src
)))
551 (defmethod ede-target-sourcecode ((this ede-target
))
552 "Return the sourcecode objects which THIS permits."
553 (let ((sc (oref this sourcetype
))
555 (while (and (listp sc
) sc
)
556 (setq rs
(cons (symbol-value (car sc
)) rs
)
563 (defun ede-adebug-project ()
564 "Run adebug against the current EDE project.
565 Display the results as a debug list."
567 (require 'data-debug
)
568 (when (ede-current-project)
569 (data-debug-new-buffer "*Analyzer ADEBUG*")
570 (data-debug-insert-object-slots (ede-current-project) "")
573 (defun ede-adebug-project-parent ()
574 "Run adebug against the current EDE parent project.
575 Display the results as a debug list."
577 (require 'data-debug
)
578 (when (ede-parent-project)
579 (data-debug-new-buffer "*Analyzer ADEBUG*")
580 (data-debug-insert-object-slots (ede-parent-project) "")
583 (defun ede-adebug-project-root ()
584 "Run adebug against the current EDE parent project.
585 Display the results as a debug list."
587 (require 'data-debug
)
589 (data-debug-new-buffer "*Analyzer ADEBUG*")
590 (data-debug-insert-object-slots (ede-toplevel) "")
597 ;; The toplevel project is a way to identify the EDE structure that belongs
598 ;; to the top of a project.
600 (defun ede-toplevel (&optional subproj
)
601 "Return the ede project which is the root of the current project.
602 Optional argument SUBPROJ indicates a subproject to start from
603 instead of the current project."
604 (or ede-object-root-project
605 (let* ((cp (or subproj
(ede-current-project))))
606 (or (and cp
(ede-project-root cp
))
608 (while (ede-parent-project cp
)
609 (setq cp
(ede-parent-project cp
)))
613 ;;; Hooks & Autoloads
615 ;; These let us watch various activities, and respond appropriately.
617 ;; (add-hook 'edebug-setup-hook
619 ;; (def-edebug-spec ede-with-projectfile
620 ;; (form def-body))))
625 ;; generated-autoload-file: "loaddefs.el"
626 ;; generated-autoload-load-name: "ede/base"
629 ;;; ede/base.el ends here