Commit | Line | Data |
---|---|---|
cb85c0d8 EL |
1 | ;;; ede/generic.el --- Base Support for generic build systems |
2 | ||
73b0cd50 | 3 | ;; Copyright (C) 2010-2011 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 | |
25 | ;; them all fully. The ede-generic.el system is the base for | |
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 | ;; | |
46 | ;; Since these projects are all so increadibly generic, a user will | |
47 | ;; need to configure some aspects of the project by hand. In order to | |
48 | ;; enable this without configuring the project objects directly (which | |
49 | ;; are auto-generated) a special ede-generic-confg object is defined to | |
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 | ;; | |
73 | ;; Optionally, any target method used by EDE can then be overriden. | |
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) | |
82 | (require 'semantic/db) | |
83 | ||
84 | ;;; Code: | |
85 | ;; | |
86 | ;; Start with the configuration system | |
87 | (defclass ede-generic-config (eieio-persistent) | |
88 | ((extension :initform ".ede") | |
89 | (file-header-line :initform ";; EDE Generic Project Configuration") | |
90 | (project :initform nil | |
91 | :documentation | |
92 | "The project this config is bound to.") | |
93 | ;; Generic customizations | |
94 | (build-command :initarg :build-command | |
95 | :initform "make -k" | |
96 | :type string | |
97 | :custom string | |
98 | :group (default build) | |
99 | :documentation | |
100 | "Command used for building this project.") | |
101 | (debug-command :initarg :debug-command | |
102 | :initform "gdb " | |
103 | :type string | |
104 | :custom string | |
105 | :group (default build) | |
106 | :documentation | |
107 | "Command used for debugging this project.") | |
108 | ;; C target customixations | |
109 | (c-include-path :initarg :c-include-path | |
110 | :initform nil | |
111 | :type list | |
112 | :custom (repeat (string :tag "Path")) | |
113 | :group c | |
114 | :documentation | |
115 | "The include path used by C/C++ projects.") | |
116 | (c-preprocessor-table :initarg :c-preprocessor-table | |
117 | :initform nil | |
118 | :type list | |
119 | :custom (repeat (cons (string :tag "Macro") | |
120 | (string :tag "Value"))) | |
121 | :group c | |
122 | :documentation | |
123 | "Preprocessor Symbols for this project.") | |
124 | (c-preprocessor-files :initarg :c-preprocessor-files | |
125 | :initform nil | |
126 | :type list | |
127 | :custom (repeat (string :tag "Include File"))) | |
128 | ) | |
129 | "User Configuration object for a generic project.") | |
130 | ||
131 | (defun ede-generic-load (dir &optional rootproj) | |
132 | "Return a Generic Project object if there is a match. | |
133 | Return nil if there isn't one. | |
134 | Argument DIR is the directory it is created for. | |
135 | ROOTPROJ is nil, since there is only one project." | |
136 | ;; Doesn't already exist, so lets make one. | |
137 | (let* ((alobj ede-constructing) | |
138 | (this nil)) | |
139 | (when (not alobj) (error "Cannot load generic project without the autoload instance")) | |
140 | ||
141 | (setq this | |
142 | (funcall (oref alobj class-sym) | |
143 | (symbol-name (oref alobj class-sym)) | |
144 | :name (file-name-nondirectory | |
145 | (directory-file-name dir)) | |
146 | :version "1.0" | |
147 | :directory (file-name-as-directory dir) | |
148 | :file (expand-file-name (oref alobj :proj-file)) )) | |
149 | (ede-add-project-to-global-list this) | |
150 | )) | |
151 | ||
152 | ;;; Base Classes for the system | |
153 | (defclass ede-generic-target (ede-target) | |
154 | ((shortname :initform "" | |
155 | :type string | |
156 | :allocation :class | |
157 | :documentation | |
158 | "Something prepended to the target name.") | |
159 | (extension :initform "" | |
160 | :type string | |
161 | :allocation :class | |
162 | :documentation | |
163 | "Regular expression representing the extension used for this target. | |
164 | subclasses of this base target will override the default value.") | |
165 | ) | |
166 | "Baseclass for all targets belonging to the generic ede system." | |
167 | :abstract t) | |
168 | ||
169 | (defclass ede-generic-project (ede-project) | |
170 | ((buildfile :initform "" | |
171 | :type string | |
172 | :allocation :class | |
173 | :documentation "The file name that identifies a project of this type. | |
174 | The class allocated value is replace by different sub classes.") | |
175 | (config :initform nil | |
176 | :type (or null ede-generic-config) | |
177 | :documentation | |
178 | "The configuration object for this project.") | |
179 | ) | |
180 | "The baseclass for all generic EDE project types." | |
181 | :abstract t) | |
182 | ||
183 | (defmethod initialize-instance ((this ede-generic-project) | |
184 | &rest fields) | |
185 | "Make sure the targets slot is bound." | |
186 | (call-next-method) | |
187 | (unless (slot-boundp this 'targets) | |
188 | (oset this :targets nil)) | |
189 | ) | |
190 | ||
191 | (defmethod ede-generic-get-configuration ((proj ede-generic-project)) | |
192 | "Return the configuration for the project PROJ." | |
193 | (let ((config (oref proj config))) | |
194 | (when (not config) | |
195 | (let ((fname (expand-file-name "EDEConfig.el" | |
196 | (oref proj :directory)))) | |
197 | (if (file-exists-p fname) | |
198 | ;; Load in the configuration | |
199 | (setq config (eieio-persistent-read fname)) | |
200 | ;; Create a new one. | |
201 | (setq config (ede-generic-config | |
202 | "Configuration" | |
203 | :file fname)) | |
204 | ;; Set initial values based on project. | |
205 | (ede-generic-setup-configuration proj config)) | |
206 | ;; Link things together. | |
207 | (oset proj config config) | |
208 | (oset config project proj))) | |
209 | config)) | |
210 | ||
211 | (defmethod ede-generic-setup-configuration ((proj ede-generic-project) config) | |
212 | "Default configuration setup method." | |
213 | nil) | |
214 | ||
215 | (defmethod ede-commit-project ((proj ede-generic-project)) | |
216 | "Commit any change to PROJ to its file." | |
217 | (let ((config (ede-generic-get-configuration proj))) | |
218 | (ede-commit config))) | |
219 | ||
220 | ;;; A list of different targets | |
221 | (defclass ede-generic-target-c-cpp (ede-generic-target) | |
222 | ((shortname :initform "C/C++") | |
223 | (extension :initform "\\([ch]\\(pp\\|xx\\|\\+\\+\\)?\\|cc\\|hh\\|CC?\\)")) | |
224 | "EDE Generic Project target for C and C++ code. | |
225 | All directories need at least one target.") | |
226 | ||
227 | (defclass ede-generic-target-el (ede-generic-target) | |
228 | ((shortname :initform "ELisp") | |
229 | (extension :initform "el")) | |
230 | "EDE Generic Project target for Emacs Lisp code. | |
231 | All directories need at least one target.") | |
232 | ||
233 | (defclass ede-generic-target-fortran (ede-generic-target) | |
234 | ((shortname :initform "Fortran") | |
235 | (extension :initform "[fF]9[05]\\|[fF]\\|for")) | |
236 | "EDE Generic Project target for Fortran code. | |
237 | All directories need at least one target.") | |
238 | ||
239 | (defclass ede-generic-target-texi (ede-generic-target) | |
240 | ((shortname :initform "Texinfo") | |
241 | (extension :initform "texi")) | |
242 | "EDE Generic Project target for texinfo code. | |
243 | All directories need at least one target.") | |
244 | ||
245 | ;; MISC must always be last since it will always match the file. | |
246 | (defclass ede-generic-target-misc (ede-generic-target) | |
247 | ((shortname :initform "Misc") | |
248 | (extension :initform "")) | |
249 | "EDE Generic Project target for Misc files. | |
250 | All directories need at least one target.") | |
251 | ||
252 | ;;; Automatic target aquisition. | |
253 | (defun ede-generic-find-matching-target (class dir targets) | |
254 | "Find a target that is a CLASS and is in DIR in the list of TARGETS." | |
255 | (let ((match nil)) | |
256 | (dolist (T targets) | |
257 | (when (and (object-of-class-p T class) | |
258 | (string= (oref T :path) dir)) | |
259 | (setq match T) | |
260 | )) | |
261 | match)) | |
262 | ||
263 | (defmethod ede-find-target ((proj ede-generic-project) buffer) | |
264 | "Find an EDE target in PROJ for BUFFER. | |
265 | If one doesn't exist, create a new one for this directory." | |
266 | (let* ((ext (file-name-extension (buffer-file-name buffer))) | |
267 | (classes (eieio-build-class-alist 'ede-generic-target t)) | |
268 | (cls nil) | |
269 | (targets (oref proj targets)) | |
270 | (dir default-directory) | |
271 | (ans nil) | |
272 | ) | |
273 | ;; Pick a matching class type. | |
274 | (when ext | |
275 | (dolist (C classes) | |
276 | (let* ((classsym (intern (car C))) | |
277 | (extreg (oref classsym extension))) | |
278 | (when (and (not (string= extreg "")) | |
279 | (string-match (concat "^" extreg "$") ext)) | |
280 | (setq cls classsym))))) | |
281 | (when (not cls) (setq cls 'ede-generic-target-misc)) | |
282 | ;; find a pre-existing matching target | |
283 | (setq ans (ede-generic-find-matching-target cls dir targets)) | |
284 | ;; Create a new instance if there wasn't one | |
285 | (when (not ans) | |
286 | (setq ans (make-instance | |
287 | cls | |
288 | :name (oref cls shortname) | |
289 | :path dir | |
290 | :source nil)) | |
291 | (object-add-to-list proj :targets ans) | |
292 | ) | |
293 | ans)) | |
294 | ||
295 | ;;; C/C++ support | |
296 | (defmethod ede-preprocessor-map ((this ede-generic-target-c-cpp)) | |
297 | "Get the pre-processor map for some generic C code." | |
298 | (let* ((proj (ede-target-parent this)) | |
299 | (root (ede-project-root proj)) | |
300 | (config (ede-generic-get-configuration proj)) | |
301 | filemap | |
302 | ) | |
303 | ;; Preprocessor files | |
304 | (dolist (G (oref config :c-preprocessor-files)) | |
305 | (let ((table (semanticdb-file-table-object | |
306 | (ede-expand-filename root G)))) | |
307 | (when table | |
308 | (when (semanticdb-needs-refresh-p table) | |
309 | (semanticdb-refresh-table table)) | |
310 | (setq filemap (append filemap (oref table lexical-table))) | |
311 | ))) | |
312 | ;; The core table | |
313 | (setq filemap (append filemap (oref config :c-preprocessor-table))) | |
314 | ||
315 | filemap | |
316 | )) | |
317 | ||
318 | (defmethod ede-system-include-path ((this ede-generic-target-c-cpp)) | |
319 | "Get the system include path used by project THIS." | |
320 | (let* ((proj (ede-target-parent this)) | |
321 | (config (ede-generic-get-configuration proj))) | |
322 | (oref config c-include-path))) | |
323 | ||
324 | ;;; Customization | |
325 | ;; | |
326 | (defmethod ede-customize ((proj ede-generic-project)) | |
327 | "Customize the EDE project PROJ." | |
328 | (let ((config (ede-generic-get-configuration proj))) | |
329 | (eieio-customize-object config))) | |
330 | ||
331 | (defmethod ede-customize ((target ede-generic-target)) | |
332 | "Customize the EDE TARGET." | |
333 | ;; Nothing unique for the targets, use the project. | |
334 | (ede-customize-project)) | |
335 | ||
336 | (defmethod eieio-done-customizing ((config ede-generic-config)) | |
337 | "Called when EIEIO is done customizing the configuration object. | |
338 | We need to go back through the old buffers, and update them with | |
339 | the new configuration." | |
340 | (ede-commit config) | |
341 | ;; Loop over all the open buffers, and re-apply. | |
342 | (ede-map-targets | |
343 | (oref config project) | |
344 | (lambda (target) | |
345 | (ede-map-target-buffers | |
346 | target | |
347 | (lambda (b) | |
348 | (with-current-buffer b | |
349 | (ede-apply-target-options))))))) | |
350 | ||
351 | (defmethod ede-commit ((config ede-generic-config)) | |
352 | "Commit all changes to the configuration to disk." | |
353 | (eieio-persistent-save config)) | |
354 | ||
355 | ;;; Creating Derived Projects: | |
356 | ;; | |
357 | ;; Derived projects need an autoloader so that EDE can find the | |
358 | ;; different projects on disk. | |
359 | (defun ede-generic-new-autoloader (internal-name external-name | |
360 | projectfile class) | |
361 | "Add a new EDE Autoload instance for identifying a generic project. | |
362 | INTERNAL-NAME is a long name that identifies thsi project type. | |
363 | EXTERNAL-NAME is a shorter human readable name to describe the project. | |
364 | PROJECTFILE is a file name that identifies a project of this type to EDE, such as | |
365 | a Makefile, or SConstruct file. | |
366 | CLASS is the EIEIO class that is used to track this project. It should subclass | |
367 | the class `ede-generic-project' project." | |
368 | (add-to-list 'ede-project-class-files | |
369 | (ede-project-autoload internal-name | |
370 | :name external-name | |
b93e37e5 | 371 | :file 'ede/generic |
cb85c0d8 EL |
372 | :proj-file projectfile |
373 | :load-type 'ede-generic-load | |
374 | :class-sym class | |
375 | :new-p nil) | |
376 | ;; Generics must go at the end, since more specific types | |
377 | ;; can create Makefiles also. | |
378 | t)) | |
379 | ||
380 | ;;;###autoload | |
381 | (defun ede-enable-generic-projects () | |
382 | "Enable generic project loaders." | |
383 | (interactive) | |
384 | (ede-generic-new-autoloader "edeproject-makefile" "Make" | |
385 | "Makefile" 'ede-generic-makefile-project) | |
386 | (ede-generic-new-autoloader "edeproject-scons" "SCons" | |
387 | "SConstruct" 'ede-generic-scons-project) | |
388 | (ede-generic-new-autoloader "edeproject-cmake" "CMake" | |
389 | "CMakeLists" 'ede-generic-cmake-project) | |
390 | ) | |
391 | ||
392 | \f | |
393 | ;;; SPECIFIC TYPES OF GENERIC BUILDS | |
394 | ;; | |
395 | ||
396 | ;;; MAKEFILE | |
397 | ||
398 | (defclass ede-generic-makefile-project (ede-generic-project) | |
399 | ((buildfile :initform "Makefile") | |
400 | ) | |
401 | "Generic Project for makefiles.") | |
402 | ||
403 | (defmethod ede-generic-setup-configuration ((proj ede-generic-makefile-project) config) | |
404 | "Setup a configuration for Make." | |
405 | (oset config build-command "make -k") | |
406 | (oset config debug-command "gdb ") | |
407 | ) | |
408 | ||
409 | ||
410 | ;;; SCONS | |
411 | (defclass ede-generic-scons-project (ede-generic-project) | |
412 | ((buildfile :initform "SConstruct") | |
413 | ) | |
414 | "Generic Project for scons.") | |
415 | ||
416 | (defmethod ede-generic-setup-configuration ((proj ede-generic-scons-project) config) | |
417 | "Setup a configuration for SCONS." | |
418 | (oset config build-command "scons") | |
419 | (oset config debug-command "gdb ") | |
420 | ) | |
421 | ||
422 | ||
423 | ;;; CMAKE | |
424 | (defclass ede-generic-cmake-project (ede-generic-project) | |
425 | ((buildfile :initform "CMakeLists") | |
426 | ) | |
427 | "Generic Project for cmake.") | |
428 | ||
429 | (defmethod ede-generic-setup-configuration ((proj ede-generic-cmake-project) config) | |
430 | "Setup a configuration for CMake." | |
431 | (oset config build-command "cmake") | |
432 | (oset config debug-command "gdb ") | |
433 | ) | |
434 | ||
435 | (provide 'ede/generic) | |
436 | ||
437 | ;; Local variables: | |
438 | ;; generated-autoload-file: "loaddefs.el" | |
439 | ;; generated-autoload-load-name: "ede/generic" | |
440 | ;; End: | |
441 | ||
442 | ;;; ede/generic.el ends here |