Commit | Line | Data |
---|---|---|
cb85c0d8 EL |
1 | ;;; ede/generic.el --- Base Support for generic build systems |
2 | ||
ab422c4d | 3 | ;; Copyright (C) 2010-2013 Free Software Foundation, Inc. |
cb85c0d8 EL |
4 | |
5 | ;; Author: Eric M. Ludlam <eric@siege-engine.com> | |
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 | ;; There are a lot of build systems out there, and EDE can't support | |
07a79ce4 | 25 | ;; them all fully. The ede/generic.el system is the base for |
cb85c0d8 EL |
26 | ;; supporting alternate build systems in a simple way, automatically. |
27 | ;; | |
28 | ;; The structure is for the ede-generic baseclass, which is augmented | |
29 | ;; by simple sub-classes that can be created by users on an as needed | |
30 | ;; basis. The generic system will have targets for many language | |
31 | ;; types, and create the targets on an as needed basis. All | |
32 | ;; sub-project types will recycle the same generic target types. | |
33 | ;; | |
34 | ;; The generic target types will only be implemented for languages | |
35 | ;; where having EDE support actually matters, with a single MISC to | |
36 | ;; represent anything else. | |
37 | ;; | |
38 | ;; TOO MANY PROJECTS DETECTED: | |
39 | ;; | |
40 | ;; If enabling ede-generic support starts identifying too many | |
41 | ;; projects, drop a file called `.ede-ignore' into any directory where | |
42 | ;; you do not want a project to be. | |
43 | ;; | |
44 | ;; Customization: | |
45 | ;; | |
53964682 | 46 | ;; Since these projects are all so incredibly generic, a user will |
cb85c0d8 EL |
47 | ;; need to configure some aspects of the project by hand. In order to |
48 | ;; enable this without configuring the project objects directly (which | |
da6062e6 | 49 | ;; are auto-generated) a special ede-generic-config object is defined to |
cb85c0d8 EL |
50 | ;; hold the basics. Generic projects will identify and use these |
51 | ;; config files. | |
52 | ;; | |
53 | ;; Adding support for new projects: | |
54 | ;; | |
55 | ;; To add support to EDE Generic for new project types is very quick. | |
56 | ;; See the end of this file for examples such as CMake and SCons. | |
57 | ;; | |
58 | ;; Support consists of one class for your project, specifying the file | |
59 | ;; name used by the project system you want to support. It also | |
60 | ;; should implement th method `ede-generic-setup-configuration' to | |
61 | ;; prepopulate the configurable portion of the generic project with | |
62 | ;; build details. | |
63 | ;; | |
64 | ;; Lastly, call `ede-generic-new-autoloader' to setup your project so | |
65 | ;; EDE can use it. | |
66 | ;; | |
67 | ;; Adding support for new types of source code: | |
68 | ;; | |
69 | ;; Sources of different types are supported with a simple class which | |
70 | ;; subclasses `ede-generic-target'. The slots `shortname' and | |
71 | ;; `extension' should be given new initial values. | |
72 | ;; | |
0b381c7e | 73 | ;; Optionally, any target method used by EDE can then be overridden. |
cb85c0d8 EL |
74 | ;; The ede-generic-target-c-cpp has some example methods setting up |
75 | ;; the pre-processor map and system include path. | |
76 | ;; | |
77 | ;; NOTE: It is not necessary to modify ede-generic.el to add any of | |
78 | ;; the above described support features. | |
79 | ||
80 | (require 'eieio-opt) | |
81 | (require 'ede) | |
62a81506 | 82 | (require 'ede/shell) |
cb85c0d8 EL |
83 | (require 'semantic/db) |
84 | ||
85 | ;;; Code: | |
86 | ;; | |
87 | ;; Start with the configuration system | |
88 | (defclass ede-generic-config (eieio-persistent) | |
89 | ((extension :initform ".ede") | |
90 | (file-header-line :initform ";; EDE Generic Project Configuration") | |
91 | (project :initform nil | |
92 | :documentation | |
93 | "The project this config is bound to.") | |
94 | ;; Generic customizations | |
95 | (build-command :initarg :build-command | |
96 | :initform "make -k" | |
97 | :type string | |
98 | :custom string | |
99 | :group (default build) | |
100 | :documentation | |
101 | "Command used for building this project.") | |
102 | (debug-command :initarg :debug-command | |
103 | :initform "gdb " | |
104 | :type string | |
105 | :custom string | |
106 | :group (default build) | |
107 | :documentation | |
108 | "Command used for debugging this project.") | |
62a81506 CY |
109 | (run-command :initarg :run-command |
110 | :initform nil | |
111 | :type (or null string) | |
112 | :custom string | |
113 | :group (default build) | |
114 | :documentation | |
115 | "Command used to run something related to this project.") | |
da6062e6 | 116 | ;; C target customizations |
cb85c0d8 EL |
117 | (c-include-path :initarg :c-include-path |
118 | :initform nil | |
119 | :type list | |
120 | :custom (repeat (string :tag "Path")) | |
121 | :group c | |
122 | :documentation | |
123 | "The include path used by C/C++ projects.") | |
124 | (c-preprocessor-table :initarg :c-preprocessor-table | |
125 | :initform nil | |
126 | :type list | |
127 | :custom (repeat (cons (string :tag "Macro") | |
128 | (string :tag "Value"))) | |
129 | :group c | |
130 | :documentation | |
131 | "Preprocessor Symbols for this project.") | |
132 | (c-preprocessor-files :initarg :c-preprocessor-files | |
133 | :initform nil | |
134 | :type list | |
135 | :custom (repeat (string :tag "Include File"))) | |
136 | ) | |
137 | "User Configuration object for a generic project.") | |
138 | ||
139 | (defun ede-generic-load (dir &optional rootproj) | |
140 | "Return a Generic Project object if there is a match. | |
141 | Return nil if there isn't one. | |
142 | Argument DIR is the directory it is created for. | |
143 | ROOTPROJ is nil, since there is only one project." | |
c7015153 | 144 | ;; Doesn't already exist, so let's make one. |
cb85c0d8 EL |
145 | (let* ((alobj ede-constructing) |
146 | (this nil)) | |
147 | (when (not alobj) (error "Cannot load generic project without the autoload instance")) | |
148 | ||
149 | (setq this | |
150 | (funcall (oref alobj class-sym) | |
151 | (symbol-name (oref alobj class-sym)) | |
152 | :name (file-name-nondirectory | |
153 | (directory-file-name dir)) | |
154 | :version "1.0" | |
155 | :directory (file-name-as-directory dir) | |
156 | :file (expand-file-name (oref alobj :proj-file)) )) | |
157 | (ede-add-project-to-global-list this) | |
158 | )) | |
159 | ||
160 | ;;; Base Classes for the system | |
161 | (defclass ede-generic-target (ede-target) | |
162 | ((shortname :initform "" | |
163 | :type string | |
164 | :allocation :class | |
165 | :documentation | |
166 | "Something prepended to the target name.") | |
167 | (extension :initform "" | |
168 | :type string | |
169 | :allocation :class | |
170 | :documentation | |
171 | "Regular expression representing the extension used for this target. | |
172 | subclasses of this base target will override the default value.") | |
173 | ) | |
174 | "Baseclass for all targets belonging to the generic ede system." | |
175 | :abstract t) | |
176 | ||
177 | (defclass ede-generic-project (ede-project) | |
178 | ((buildfile :initform "" | |
179 | :type string | |
180 | :allocation :class | |
181 | :documentation "The file name that identifies a project of this type. | |
182 | The class allocated value is replace by different sub classes.") | |
183 | (config :initform nil | |
184 | :type (or null ede-generic-config) | |
185 | :documentation | |
186 | "The configuration object for this project.") | |
187 | ) | |
188 | "The baseclass for all generic EDE project types." | |
189 | :abstract t) | |
190 | ||
191 | (defmethod initialize-instance ((this ede-generic-project) | |
192 | &rest fields) | |
193 | "Make sure the targets slot is bound." | |
194 | (call-next-method) | |
195 | (unless (slot-boundp this 'targets) | |
196 | (oset this :targets nil)) | |
197 | ) | |
198 | ||
199 | (defmethod ede-generic-get-configuration ((proj ede-generic-project)) | |
200 | "Return the configuration for the project PROJ." | |
201 | (let ((config (oref proj config))) | |
202 | (when (not config) | |
203 | (let ((fname (expand-file-name "EDEConfig.el" | |
204 | (oref proj :directory)))) | |
205 | (if (file-exists-p fname) | |
206 | ;; Load in the configuration | |
62a81506 | 207 | (setq config (eieio-persistent-read fname 'ede-generic-config)) |
cb85c0d8 EL |
208 | ;; Create a new one. |
209 | (setq config (ede-generic-config | |
210 | "Configuration" | |
211 | :file fname)) | |
212 | ;; Set initial values based on project. | |
213 | (ede-generic-setup-configuration proj config)) | |
214 | ;; Link things together. | |
215 | (oset proj config config) | |
216 | (oset config project proj))) | |
217 | config)) | |
218 | ||
219 | (defmethod ede-generic-setup-configuration ((proj ede-generic-project) config) | |
220 | "Default configuration setup method." | |
221 | nil) | |
222 | ||
223 | (defmethod ede-commit-project ((proj ede-generic-project)) | |
224 | "Commit any change to PROJ to its file." | |
225 | (let ((config (ede-generic-get-configuration proj))) | |
226 | (ede-commit config))) | |
227 | ||
228 | ;;; A list of different targets | |
229 | (defclass ede-generic-target-c-cpp (ede-generic-target) | |
230 | ((shortname :initform "C/C++") | |
231 | (extension :initform "\\([ch]\\(pp\\|xx\\|\\+\\+\\)?\\|cc\\|hh\\|CC?\\)")) | |
232 | "EDE Generic Project target for C and C++ code. | |
233 | All directories need at least one target.") | |
234 | ||
235 | (defclass ede-generic-target-el (ede-generic-target) | |
236 | ((shortname :initform "ELisp") | |
237 | (extension :initform "el")) | |
238 | "EDE Generic Project target for Emacs Lisp code. | |
239 | All directories need at least one target.") | |
240 | ||
241 | (defclass ede-generic-target-fortran (ede-generic-target) | |
242 | ((shortname :initform "Fortran") | |
243 | (extension :initform "[fF]9[05]\\|[fF]\\|for")) | |
244 | "EDE Generic Project target for Fortran code. | |
245 | All directories need at least one target.") | |
246 | ||
247 | (defclass ede-generic-target-texi (ede-generic-target) | |
248 | ((shortname :initform "Texinfo") | |
249 | (extension :initform "texi")) | |
250 | "EDE Generic Project target for texinfo code. | |
251 | All directories need at least one target.") | |
252 | ||
253 | ;; MISC must always be last since it will always match the file. | |
254 | (defclass ede-generic-target-misc (ede-generic-target) | |
255 | ((shortname :initform "Misc") | |
256 | (extension :initform "")) | |
257 | "EDE Generic Project target for Misc files. | |
258 | All directories need at least one target.") | |
259 | ||
91af3942 | 260 | ;;; Automatic target acquisition. |
cb85c0d8 EL |
261 | (defun ede-generic-find-matching-target (class dir targets) |
262 | "Find a target that is a CLASS and is in DIR in the list of TARGETS." | |
263 | (let ((match nil)) | |
264 | (dolist (T targets) | |
265 | (when (and (object-of-class-p T class) | |
266 | (string= (oref T :path) dir)) | |
267 | (setq match T) | |
268 | )) | |
269 | match)) | |
270 | ||
271 | (defmethod ede-find-target ((proj ede-generic-project) buffer) | |
272 | "Find an EDE target in PROJ for BUFFER. | |
273 | If one doesn't exist, create a new one for this directory." | |
274 | (let* ((ext (file-name-extension (buffer-file-name buffer))) | |
275 | (classes (eieio-build-class-alist 'ede-generic-target t)) | |
276 | (cls nil) | |
277 | (targets (oref proj targets)) | |
278 | (dir default-directory) | |
279 | (ans nil) | |
280 | ) | |
281 | ;; Pick a matching class type. | |
282 | (when ext | |
283 | (dolist (C classes) | |
284 | (let* ((classsym (intern (car C))) | |
285 | (extreg (oref classsym extension))) | |
286 | (when (and (not (string= extreg "")) | |
287 | (string-match (concat "^" extreg "$") ext)) | |
288 | (setq cls classsym))))) | |
289 | (when (not cls) (setq cls 'ede-generic-target-misc)) | |
290 | ;; find a pre-existing matching target | |
291 | (setq ans (ede-generic-find-matching-target cls dir targets)) | |
292 | ;; Create a new instance if there wasn't one | |
293 | (when (not ans) | |
294 | (setq ans (make-instance | |
295 | cls | |
296 | :name (oref cls shortname) | |
297 | :path dir | |
298 | :source nil)) | |
299 | (object-add-to-list proj :targets ans) | |
300 | ) | |
301 | ans)) | |
302 | ||
303 | ;;; C/C++ support | |
304 | (defmethod ede-preprocessor-map ((this ede-generic-target-c-cpp)) | |
305 | "Get the pre-processor map for some generic C code." | |
306 | (let* ((proj (ede-target-parent this)) | |
307 | (root (ede-project-root proj)) | |
308 | (config (ede-generic-get-configuration proj)) | |
309 | filemap | |
310 | ) | |
311 | ;; Preprocessor files | |
312 | (dolist (G (oref config :c-preprocessor-files)) | |
313 | (let ((table (semanticdb-file-table-object | |
314 | (ede-expand-filename root G)))) | |
315 | (when table | |
316 | (when (semanticdb-needs-refresh-p table) | |
317 | (semanticdb-refresh-table table)) | |
318 | (setq filemap (append filemap (oref table lexical-table))) | |
319 | ))) | |
320 | ;; The core table | |
321 | (setq filemap (append filemap (oref config :c-preprocessor-table))) | |
322 | ||
323 | filemap | |
324 | )) | |
325 | ||
326 | (defmethod ede-system-include-path ((this ede-generic-target-c-cpp)) | |
327 | "Get the system include path used by project THIS." | |
328 | (let* ((proj (ede-target-parent this)) | |
329 | (config (ede-generic-get-configuration proj))) | |
330 | (oref config c-include-path))) | |
331 | ||
62a81506 CY |
332 | ;;; Commands |
333 | ;; | |
334 | (defmethod project-compile-project ((proj ede-generic-project) &optional command) | |
335 | "Compile the entire current project PROJ. | |
336 | Argument COMMAND is the command to use when compiling." | |
337 | (let* ((config (ede-generic-get-configuration proj)) | |
338 | (comp (oref config :build-command))) | |
339 | (compile comp))) | |
340 | ||
341 | (defmethod project-compile-target ((obj ede-generic-target) &optional command) | |
342 | "Compile the current target OBJ. | |
343 | Argument COMMAND is the command to use for compiling the target." | |
344 | (project-compile-project (ede-current-project) command)) | |
345 | ||
346 | (defmethod project-debug-target ((target ede-generic-target)) | |
347 | "Run the current project derived from TARGET in a debugger." | |
348 | (let* ((proj (ede-target-parent target)) | |
349 | (config (ede-generic-get-configuration proj)) | |
350 | (debug (oref config :debug-command)) | |
351 | (cmd (read-from-minibuffer | |
352 | "Debug Command: " | |
353 | debug)) | |
354 | (cmdsplit (split-string cmd " " t)) | |
355 | ;; @TODO - this depends on the user always typing in something good | |
356 | ;; like "gdb" or "dbx" which also exists as a useful Emacs command. | |
357 | ;; Is there a better way? | |
358 | (cmdsym (intern-soft (car cmdsplit)))) | |
359 | (call-interactively cmdsym t))) | |
360 | ||
361 | (defmethod project-run-target ((target ede-generic-target)) | |
362 | "Run the current project derived from TARGET." | |
62a81506 CY |
363 | (let* ((proj (ede-target-parent target)) |
364 | (config (ede-generic-get-configuration proj)) | |
365 | (run (concat "./" (oref config :run-command))) | |
366 | (cmd (read-from-minibuffer "Run (like this): " run))) | |
367 | (ede-shell-run-something target cmd))) | |
368 | ||
cb85c0d8 EL |
369 | ;;; Customization |
370 | ;; | |
371 | (defmethod ede-customize ((proj ede-generic-project)) | |
372 | "Customize the EDE project PROJ." | |
373 | (let ((config (ede-generic-get-configuration proj))) | |
374 | (eieio-customize-object config))) | |
375 | ||
376 | (defmethod ede-customize ((target ede-generic-target)) | |
377 | "Customize the EDE TARGET." | |
378 | ;; Nothing unique for the targets, use the project. | |
379 | (ede-customize-project)) | |
380 | ||
381 | (defmethod eieio-done-customizing ((config ede-generic-config)) | |
382 | "Called when EIEIO is done customizing the configuration object. | |
383 | We need to go back through the old buffers, and update them with | |
384 | the new configuration." | |
385 | (ede-commit config) | |
386 | ;; Loop over all the open buffers, and re-apply. | |
387 | (ede-map-targets | |
388 | (oref config project) | |
389 | (lambda (target) | |
390 | (ede-map-target-buffers | |
391 | target | |
392 | (lambda (b) | |
393 | (with-current-buffer b | |
394 | (ede-apply-target-options))))))) | |
395 | ||
396 | (defmethod ede-commit ((config ede-generic-config)) | |
397 | "Commit all changes to the configuration to disk." | |
398 | (eieio-persistent-save config)) | |
399 | ||
400 | ;;; Creating Derived Projects: | |
401 | ;; | |
402 | ;; Derived projects need an autoloader so that EDE can find the | |
403 | ;; different projects on disk. | |
404 | (defun ede-generic-new-autoloader (internal-name external-name | |
405 | projectfile class) | |
406 | "Add a new EDE Autoload instance for identifying a generic project. | |
40ba43b4 | 407 | INTERNAL-NAME is a long name that identifies this project type. |
cb85c0d8 EL |
408 | EXTERNAL-NAME is a shorter human readable name to describe the project. |
409 | PROJECTFILE is a file name that identifies a project of this type to EDE, such as | |
410 | a Makefile, or SConstruct file. | |
411 | CLASS is the EIEIO class that is used to track this project. It should subclass | |
412 | the class `ede-generic-project' project." | |
62a81506 CY |
413 | (ede-add-project-autoload |
414 | (ede-project-autoload internal-name | |
415 | :name external-name | |
416 | :file 'ede/generic | |
417 | :proj-file projectfile | |
418 | :load-type 'ede-generic-load | |
419 | :class-sym class | |
420 | :new-p nil | |
421 | :safe-p nil) ; @todo - could be | |
422 | ; safe if we do something | |
423 | ; about the loading of the | |
424 | ; generic config file. | |
425 | ;; Generics must go at the end, since more specific types | |
426 | ;; can create Makefiles also. | |
427 | 'generic)) | |
cb85c0d8 EL |
428 | |
429 | ;;;###autoload | |
430 | (defun ede-enable-generic-projects () | |
431 | "Enable generic project loaders." | |
432 | (interactive) | |
62a81506 | 433 | (ede-generic-new-autoloader "generic-makefile" "Make" |
cb85c0d8 | 434 | "Makefile" 'ede-generic-makefile-project) |
62a81506 | 435 | (ede-generic-new-autoloader "generic-scons" "SCons" |
cb85c0d8 | 436 | "SConstruct" 'ede-generic-scons-project) |
62a81506 | 437 | (ede-generic-new-autoloader "generic-cmake" "CMake" |
cb85c0d8 EL |
438 | "CMakeLists" 'ede-generic-cmake-project) |
439 | ) | |
440 | ||
441 | \f | |
442 | ;;; SPECIFIC TYPES OF GENERIC BUILDS | |
443 | ;; | |
444 | ||
445 | ;;; MAKEFILE | |
446 | ||
447 | (defclass ede-generic-makefile-project (ede-generic-project) | |
448 | ((buildfile :initform "Makefile") | |
449 | ) | |
450 | "Generic Project for makefiles.") | |
451 | ||
452 | (defmethod ede-generic-setup-configuration ((proj ede-generic-makefile-project) config) | |
453 | "Setup a configuration for Make." | |
454 | (oset config build-command "make -k") | |
455 | (oset config debug-command "gdb ") | |
456 | ) | |
457 | ||
458 | ||
459 | ;;; SCONS | |
460 | (defclass ede-generic-scons-project (ede-generic-project) | |
461 | ((buildfile :initform "SConstruct") | |
462 | ) | |
463 | "Generic Project for scons.") | |
464 | ||
465 | (defmethod ede-generic-setup-configuration ((proj ede-generic-scons-project) config) | |
466 | "Setup a configuration for SCONS." | |
467 | (oset config build-command "scons") | |
468 | (oset config debug-command "gdb ") | |
469 | ) | |
470 | ||
471 | ||
472 | ;;; CMAKE | |
473 | (defclass ede-generic-cmake-project (ede-generic-project) | |
474 | ((buildfile :initform "CMakeLists") | |
475 | ) | |
476 | "Generic Project for cmake.") | |
477 | ||
478 | (defmethod ede-generic-setup-configuration ((proj ede-generic-cmake-project) config) | |
479 | "Setup a configuration for CMake." | |
480 | (oset config build-command "cmake") | |
481 | (oset config debug-command "gdb ") | |
482 | ) | |
483 | ||
484 | (provide 'ede/generic) | |
485 | ||
486 | ;; Local variables: | |
487 | ;; generated-autoload-file: "loaddefs.el" | |
488 | ;; generated-autoload-load-name: "ede/generic" | |
489 | ;; End: | |
490 | ||
491 | ;;; ede/generic.el ends here |