diff lisp/cedet/ede/cpp-root.el @ 104496:8c4870c15962

* cedet/ede.el, cedet/ede/*.el: New files. * cedet/cedet.el: Require ede. * cedet/semantic/symref/filter.el (semantic-symref-hits-in-region): Require semantic/idle.
author Chong Yidong <cyd@stupidchicken.com>
date Sun, 20 Sep 2009 15:06:05 +0000
parents
children 801834237f9c
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/lisp/cedet/ede/cpp-root.el	Sun Sep 20 15:06:05 2009 +0000
@@ -0,0 +1,515 @@
+;;; ede/cpp-root.el --- A simple way to wrap a C++ project with a single root
+
+;; Copyright (C) 2007, 2008, 2009 Free Software Foundation, Inc.
+
+;; Author: Eric M. Ludlam <eric@siege-engine.com>
+
+;; This file is part of GNU Emacs.
+
+;; GNU Emacs is free software: you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation, either version 3 of the License, or
+;; (at your option) any later version.
+
+;; GNU Emacs is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with GNU Emacs.  If not, see <http://www.gnu.org/licenses/>.
+
+;;; Commentary:
+;;
+;; NOTE: ede-cpp-root.el has been commented so as to also make it
+;;       useful for learning how to make similar project types.
+;;
+;; Not everyone can use automake, or an EDE project type.  For
+;; pre-existing code, it is often helpful jut to be able to wrap the
+;; whole thing up in as simple a way as possible.
+;;
+;; The cpp-root project type will allow you to create a single object
+;; with no save-file in your .emacs file that will be recognized, and
+;; provide a way to easilly allow EDE to provide Semantic with the
+;; ability to find header files, and other various source files
+;; quickly.
+;;
+;; The cpp-root class knows a few things about C++ projects, such as
+;; the prevalence of "include" directories, and typical file-layout
+;; stuff.  If this isn't sufficient, you can subclass
+;; `ede-cpp-root-project' and add your own tweaks in just a few lines.
+;; See the end of this file for an example.
+;;
+;;; EXAMPLE
+;;
+;; Add this to your .emacs file, modifying apropriate bits as needed.
+;;
+;; (ede-cpp-root-project "SOMENAME" :file "/dir/to/some/file")
+;;
+;; Replace SOMENAME with whatever name you want, and the filename to
+;; an actual file at the root of your project.  It might be a
+;; Makefile, a README file.  Whatever.  It doesn't matter.  It's just
+;; a key to hang the rest of EDE off of.
+;;
+;; The most likely reason to create this project, is to help make
+;; finding files within the project faster.  In conjunction with
+;; Semantic completion, having a short include path is key.  You can
+;; override the include path like this:
+;;
+;; (ede-cpp-root-project "NAME" :file "FILENAME"
+;;     :include-path '( "/include" "../include" "/c/include" )
+;;     :system-include-path '( "/usr/include/c++/3.2.2/" )
+;;     :spp-table '( ("MOOSE" . "")
+;;                   ("CONST" . "const") )
+;;     :spp-files '( "include/config.h" )
+;;     )
+;;
+;;  In this case each item in the include path list is searched.  If
+;;  the directory starts with "/", then that expands to the project
+;;  root directory.  If a directory does not start with "/", then it
+;;  is relative to the default-directory of the current buffer when
+;;  the file name is expanded.
+;;
+;;  The include path only affects C/C++ header files.  Use the slot
+;;  :header-match-regexp to change it.
+;;
+;;  The :system-include-path allows you to specify full directory
+;;  names to include directories where system header files can be
+;;  found.  These will be applied to files in this project only.
+;;
+;;  The :spp-table provides a list of project specific #define style
+;;  macros that are unique to this project, passed in to the compiler
+;;  on the command line, or are in special headers.
+;;
+;;  The :spp-files option is like :spp-table, except you can provide a
+;;  file name for a header in your project where most of your CPP
+;;  macros reside.  Doing this can be easier than listing everything in
+;;  the :spp-table option.  The files listed in :spp-files should not
+;;  start with a /, and are relative to something in :include-path.;;
+;;
+;; If you want to override the file-finding tool with your own
+;; function you can do this:
+;;
+;; (ede-cpp-root-project "NAME" :file "FILENAME" :locate-fcn 'MYFCN)
+;;
+;; Where FILENAME is a file in the root directory of the project.
+;; Where MYFCN is a symbol for a function.  See:
+;;
+;; M-x describe-class RET ede-cpp-root-project RET
+;;
+;; for documentation about the locate-fcn extension.
+;;
+;;; ADVANCED EXAMPLE
+;;
+;; If the cpp-root project style is right for you, but you want a
+;; dynamic loader, instead of hard-coding values in your .emacs, you
+;; can do that too, but you will need to write some lisp code.
+;;
+;; To do that, you need to add an entry to the
+;; `ede-project-class-files' list, and also provide two functions to
+;; teach EDE how to load your project pattern
+;;
+;; It would oook like this:
+;;
+;; (defun MY-FILE-FOR-DIR (&optional dir)
+;;   "Return a full file name to the project file stored in DIR."
+;;   <write your code here, or return nil>
+;;   )
+;;
+;; (defun MY-ROOT-FCN ()
+;;   "Return the root directory for `default-directory'"
+;;   ;; You might be able to use `ede-cpp-root-project-root'.
+;;   )
+;;
+;; (defun MY-LOAD (dir)
+;;   "Load a project of type `cpp-root' for the directory DIR.
+;; Return nil if there isn't one."
+;;   (ede-cpp-root-project "NAME" :file (expand-file-name "FILE" dir)
+;;                                :locate-fcn 'MYFCN)
+;;   )
+;;
+;; (add-to-list 'ede-project-class-files
+;; 	     (ede-project-autoload "cpp-root"
+;; 	      :name "CPP ROOT"
+;; 	      :file 'ede-cpp-root
+;; 	      :proj-file 'MY-FILE-FOR-DIR
+;;            :proj-root 'MY-ROOT-FCN
+;; 	      :load-type 'MY-LOAD
+;; 	      :class-sym 'ede-cpp-root)
+;; 	     t)
+;;
+;;; TODO
+;;
+;; Need a way to reconfigure a project, and have it affect all open buffers.
+;; From Tobias Gerdin:
+;;
+;;   >>3) Is there any way to refresh a ede-cpp-root-project dynamically? I have
+;;   >>some file open part of the project, fiddle with the include paths and would
+;;   >>like the open buffer to notice this when I re-evaluate the
+;;   >>ede-cpp-root-project constructor.
+;;   >
+;;   > Another good idea.  The easy way is to "revert-buffer" as needed.  The
+;;   > ede "project local variables" does this already, so it should be easy
+;;   > to adapt something.
+;;
+;;   I actually tried reverting the buffer but Semantic did not seem to pick
+;;   up the differences (the "include summary" reported the same include paths).
+
+(require 'ede)
+
+(defvar semantic-lex-spp-project-macro-symbol-obarray)
+(declare-function semantic-lex-make-spp-table "semantic/lex-spp")
+(declare-function semanticdb-file-table-object "semantic/db")
+(declare-function semanticdb-needs-refresh-p "semantic/db")
+(declare-function semanticdb-refresh-table "semantic/db")
+
+;;; Code:
+
+;;; PROJECT CACHE:
+;;
+;; cpp-root projects are created in a .emacs or other config file, but
+;; there still needs to be a way for a particular file to be
+;; identified against it.  The cache is where we look to map a file
+;; against a project.
+;;
+;; Setting up a simple in-memory cache of active projects allows the
+;; user to re-load their configuration file several times without
+;; messing up the active project set.
+;;
+(defvar ede-cpp-root-project-list nil
+  "List of projects created by option `ede-cpp-root-project'.")
+
+(defun ede-cpp-root-file-existing (dir)
+  "Find a cpp-root project in the list of cpp-root projects.
+DIR is the directory to search from."
+  (let ((projs ede-cpp-root-project-list)
+	(ans nil))
+    (while (and projs (not ans))
+      (let ((root (ede-project-root-directory (car projs))))
+	(when (string-match (concat "^" (regexp-quote root)) dir)
+	  (setq ans (car projs))))
+      (setq projs (cdr projs)))
+    ans))
+
+;;; PROJECT AUTOLOAD CONFIG
+;;
+;; Each project type registers itself into the project-class list.
+;; This way, each time a file is loaded, EDE can map that file to a
+;; project.  This project type checks files against the internal cache
+;; of projects created by the user.
+;;
+;; EDE asks two kinds of questions.  One is, does this DIR belong to a
+;; project.  If it does, it then asks, what is the ROOT directory to
+;; the project in DIR.  This is easy for cpp-root projects, but more
+;; complex for multiply nested projects.
+;;
+;; If EDE finds out that a project exists for DIR, it then loads that
+;; project.  The LOAD routine can either create a new project object
+;; (if it needs to load it off disk) or more likely can return an
+;; existing object for the discovered directory.  cpp-root always uses
+;; the second case.
+
+(defun ede-cpp-root-project-file-for-dir (&optional dir)
+  "Return a full file name to the project file stored in DIR."
+  (let ((proj (ede-cpp-root-file-existing dir)))
+    (when proj (oref proj :file))))
+
+(defvar ede-cpp-root-count 0
+  "Count number of hits to the cpp root thing.
+This is a debugging variable to test various optimizations in file
+lookup in the main EDE logic.")
+
+;;;###autoload
+(defun ede-cpp-root-project-root (&optional dir)
+  "Get the root directory for DIR."
+  (let ((projfile (ede-cpp-root-project-file-for-dir
+		   (or dir default-directory))))
+    (setq ede-cpp-root-count (1+ ede-cpp-root-count))
+    ;(debug)
+    (when projfile
+      (file-name-directory projfile))))
+
+(defun ede-cpp-root-load (dir &optional rootproj)
+  "Return a CPP root object if you created one.
+Return nil if there isn't one.
+Argument DIR is the directory it is created for.
+ROOTPROJ is nil, since there is only one project."
+  ;; Snoop through our master list.
+  (ede-cpp-root-file-existing dir))
+
+;;; CLASSES
+;;
+;; EDE sets up projects with two kinds of objects.
+;;
+;; The PROJECT is a class that represents everything under a directory
+;; hierarchy.  A TARGET represents a subset of files within a project.
+;; A project can have multiple targets, and multiple sub-projects.
+;; Sub projects should map to sub-directories.
+;;
+;; The CPP-ROOT project maps any file in C or C++ mode to a target for
+;; C files.
+;;
+;; When creating a custom project the project developer an opportunity
+;; to run code to setup various tools whenever an associated buffer is
+;; loaded.  The CPP-ROOT project spends most of its time setting up C
+;; level include paths, and PreProcessor macro tables.
+
+(defclass ede-cpp-root-target (ede-target)
+  ()
+  "EDE cpp-root project target.
+All directories need at least one target.")
+
+(defclass ede-cpp-root-project (ede-project eieio-instance-tracker)
+  ((tracking-symbol :initform 'ede-cpp-root-project-list)
+   (include-path :initarg :include-path
+		 :initform '( "/include" "../include/" )
+		 :type list
+		 :documentation
+		 "The default locate function expands filenames within a project.
+If a header file (.h, .hh, etc) name is expanded, and
+the :locate-fcn slot is nil, then the include path is checked
+first, and other directories are ignored.  For very large
+projects, this optimization can save a lot of time.
+
+Directory names in the path can be relative to the current
+buffer's `default-directory' (not starting with a /).  Directories
+that are relative to the project's root should start with a /, such
+as  \"/include\", meaning the directory `include' off the project root
+directory.")
+   (system-include-path :initarg :system-include-path
+			:initform nil
+			:type list
+			:documentation
+			"The system include path for files in this project.
+C files initialized in an ede-cpp-root-project have their semantic
+system include path set to this value.  If this is nil, then the
+semantic path is not modified.")
+   (spp-table :initarg :spp-table
+	      :initform nil
+	      :type list
+	      :documentation
+	      "C Preprocessor macros for your files.
+Preprocessor symbols will be used while parsing your files.
+These macros might be passed in through the command line compiler, or
+are critical symbols derived from header files.  Providing header files
+macro values through this slot improves accuracy and performance.
+Use `:spp-files' to use these files directly.")
+   (spp-files :initarg :spp-files
+	      :initform nil
+	      :type list
+	      :documentation
+	      "C header file with Preprocessor macros for your files.
+The PreProcessor symbols appearing in these files will be used while
+parsing files in this project.
+See `semantic-lex-c-preprocessor-symbol-map' for more on how this works.")
+   (header-match-regexp :initarg :header-match-regexp
+			:initform
+			"\\.\\(h\\(h\\|xx\\|pp\\|\\+\\+\\)?\\|H\\)$\\|\\<\\w+$"
+			:type string
+			:documentation
+			"Regexp used to identify C/C++ header files.")
+   (locate-fcn :initarg :locate-fcn
+	       :initform nil
+	       :type (or null function)
+	       :documentation
+	       "The locate function can be used in place of
+`ede-expand-filename' so you can quickly customize your custom target
+to use specialized local routines instead of the EDE routines.
+The function symbol must take two arguments:
+  NAME - The name of the file to find.
+  DIR - The directory root for this cpp-root project.
+
+It should return the fully qualified file name passed in from NAME.  If that file does not
+exist, it should return nil."
+	       )
+   )
+  "EDE cpp-root project class.
+Each directory needs a a project file to control it.")
+
+;;; INIT
+;;
+;; Most projects use `initialize-instance' to do special setup
+;; on the object when it is created.  In this case, EDE-CPP-ROOT can
+;; find previous copies of this project, and make sure that one of the
+;; objects is deleted.
+
+(defmethod initialize-instance ((this ede-cpp-root-project)
+				&rest fields)
+  "Make sure the :file is fully expanded."
+  ;; Add ourselves to the master list
+  (call-next-method)
+  (let ((f (expand-file-name (oref this :file))))
+    ;; Remove any previous entries from the main list.
+    (let ((old (eieio-instance-tracker-find (file-name-directory f)
+					    :directory 'ede-cpp-root-project-list)))
+      ;; This is safe, because :directory isn't filled in till later.
+      (when (and old (not (eq old this)))
+	(delete-instance old)))
+    ;; Basic initialization.
+    (when (or (not (file-exists-p f))
+	      (file-directory-p f))
+      (delete-instance this)
+      (error ":file for ede-cpp-root must be a file."))
+    (oset this :file f)
+    (oset this :directory (file-name-directory f))
+    (ede-project-directory-remove-hash (file-name-directory f))
+    (ede-add-project-to-global-list this)
+    (unless (slot-boundp this 'targets)
+      (oset this :targets nil))
+    ;; We need to add ourselves to the master list.
+    ;;(setq ede-projects (cons this ede-projects))
+    ))
+
+;;; SUBPROJ Management.
+;;
+;; This is a way to allow a subdirectory to point back to the root
+;; project, simplifying authoring new single-point projects.
+
+(defmethod ede-find-subproject-for-directory ((proj ede-cpp-root-project)
+					      dir)
+  "Return PROJ, for handling all subdirs below DIR."
+  proj)
+
+;;; TARGET MANAGEMENT
+;;
+;; Creating new targets on a per directory basis is a good way to keep
+;; files organized.  See ede-emacs for an example with multiple file
+;; types.
+(defmethod ede-find-target ((proj ede-cpp-root-project) buffer)
+  "Find an EDE target in PROJ for BUFFER.
+If one doesn't exist, create a new one for this directory."
+  (let* ((targets (oref proj targets))
+	 (dir default-directory)
+	 (ans (object-assoc dir :path targets))
+	 )
+    (when (not ans)
+      (setq ans (ede-cpp-root-target dir
+                 :name (file-name-nondirectory
+			(directory-file-name dir))
+		 :path dir
+		 :source nil))
+      (object-add-to-list proj :targets ans)
+      )
+    ans))
+
+;;; FILE NAMES
+;;
+;; One of the more important jobs of EDE is to find files in a
+;; directory structure.  cpp-root has tricks it knows about how most C
+;; projects are set up with include paths.
+;;
+;; This tools also uses the ede-locate setup for augmented file name
+;; lookup using external tools.
+(defmethod ede-expand-filename-impl ((proj ede-cpp-root-project) name)
+  "Within this project PROJ, find the file NAME.
+This knows details about or source tree."
+  ;; The slow part of the original is looping over subprojects.
+  ;; This version has no subprojects, so this will handle some
+  ;; basic cases.
+  (let ((ans (call-next-method)))
+    (unless ans
+      (let* ((lf (oref proj locate-fcn))
+	     (dir (file-name-directory (oref proj file))))
+	(if lf
+	    (setq ans (funcall lf name dir))
+	  (if (ede-cpp-root-header-file-p proj name)
+	      ;; Else, use our little hack.
+	      (let ((ip (oref proj include-path))
+		    (tmp nil))
+		(while ip
+		  ;; Translate
+		  (setq tmp (ede-cpp-root-translate-file proj (car ip)))
+		  ;; Test this name.
+		  (setq tmp (expand-file-name name tmp))
+		  (if (file-exists-p tmp)
+		      (setq ans tmp))
+		  (setq ip (cdr ip)) ))
+	    ;; Else, do the usual.
+	    (setq ans (call-next-method)))
+	  )))
+    (or ans (call-next-method))))
+
+(defmethod ede-project-root ((this ede-cpp-root-project))
+  "Return my root."
+  this)
+
+(defmethod ede-project-root-directory ((this ede-cpp-root-project))
+  "Return my root."
+  (file-name-directory (oref this file)))
+
+;;; C/CPP SPECIFIC CODE
+;;
+;; The following code is specific to setting up header files,
+;; include lists, and Preprocessor symbol tables.
+
+(defmethod ede-cpp-root-header-file-p ((proj ede-cpp-root-project) name)
+  "Non nil if in PROJ the filename NAME is a header."
+  (save-match-data
+    (string-match (oref proj header-match-regexp) name)))
+
+(defmethod ede-cpp-root-translate-file ((proj ede-cpp-root-project) filename)
+  "For PROJ, translate a user specified FILENAME.
+This is for project include paths and spp source files."
+  ;; Step one: Root of this project.
+  (let ((dir (file-name-directory (oref proj file))))
+
+    ;; Step two: Analyze first char, and rehost
+    (if (and (not (string= filename "")) (= (aref filename 0) ?/))
+	;; Check relative to root of project
+	(setq filename (expand-file-name (substring filename 1)
+					 dir))
+      ;; Relative to current directory.
+      (setq filename (expand-file-name filename)))
+
+    filename))
+
+(defmethod ede-set-project-variables ((project ede-cpp-root-project) &optional buffer)
+  "Set variables local to PROJECT in BUFFER.
+Also set up the lexical preprocessor map."
+  (call-next-method)
+  (when (and (featurep 'semantic-c) (featurep 'semantic-lex-spp))
+    (setq semantic-lex-spp-project-macro-symbol-obarray
+	  (semantic-lex-make-spp-table (oref project spp-table)))
+    ))
+
+(defmethod ede-system-include-path ((this ede-cpp-root-project))
+  "Get the system include path used by project THIS."
+  (oref this system-include-path))
+
+(defmethod ede-preprocessor-map ((this ede-cpp-root-project))
+  "Get the pre-processor map for project THIS."
+  (require 'semantic/db)
+  (let ((spp (oref this spp-table))
+	(root (ede-project-root this))
+	)
+    (mapc
+     (lambda (F)
+       (let* ((expfile (ede-expand-filename root F))
+	      (table (when expfile
+		       (semanticdb-file-table-object expfile)))
+	      )
+	 (when (not table)
+	   (message "Cannot find file %s in project." F))
+	 (when (and table (semanticdb-needs-refresh-p table))
+	   (semanticdb-refresh-table table))
+	 (setq spp (append spp (oref table lexical-table)))))
+     (oref this spp-files))
+    spp))
+
+(defmethod ede-system-include-path ((this ede-cpp-root-target))
+  "Get the system include path used by project THIS."
+  (ede-system-include-path (ede-target-parent this)))
+
+(defmethod ede-preprocessor-map ((this ede-cpp-root-target))
+  "Get the pre-processor map for project THIS."
+  (ede-preprocessor-map  (ede-target-parent this)))
+
+(provide 'ede/cpp-root)
+
+;; Local variables:
+;; generated-autoload-file: "loaddefs.el"
+;; generated-autoload-feature: ede/loaddefs
+;; generated-autoload-load-name: "ede/cpp-root"
+;; End:
+
+;;; ede/cpp-root.el ends here