Elisp is an interpreted language. You can put version-specific code in your
.emacs, but protect it by testing at load time that it's operating on the correct version.
This code will work in all versions because
(shiny-new-feature) is only evaluated when
(is-new-feature-available) returns true. Much of this answer is devoted to how to implement
Coping with different sets of features
It's better to test whether a feature is available than to test the Emacs version. Sometimes the feature may be available as an optional package. If you want to run code in XEmacs or other Emacs variant, it may have acquired the same features in different versions. Use the function
boundp to test if a variable is available, and
fboundp to test if a function is available.
For example, the following snippet binds a key to toggle
visual-line-mode if available, and
(global-set-key "\eml" (if (fboundp 'visual-line-mode)
Sometimes, rather than test for the feature, it's easier to run a small piece of code and ignore any errors due to undefined functions, invalid arguments, etc. Don't do this for large amounts of code, as this will make your code very hard to debug.
For example, I don't want to see a toolbar. Older versions of Emacs didn't have them at all. GNU Emacs and XEmacs added that feature in different ways and made it the default. Here's how I turn them off. The
set-specifier function is specific to XEmacs, and
default-toolbar-visible-p is specific to recent enough versions of Emacs; using
condition-case takes care of both requirements. GNU Emacs provides a dedicated function so I merely test if that function is available.
;; For XEmacs
(set-specifier default-toolbar-visible-p nil)
;; For GNU Emacs
(if (fboundp 'tool-bar-mode)
Some face names change over versions. Use
facep to test the availability of a face name.
(let ((face (if (facep 'mode-line) 'mode-line 'modeline)))
(set-face-background face …))
Sometimes you may want to load a nice package if present, and do nothing if the package isn't available.
require has an optional argument for that.
(require 'tex-site nil t) ;; Load AUCTeX if available
This argument was introduced in GNU Emacs 20.4 and isn't available in XEmacs, so if you want to go that far back, you'll have to either wrap it in
condition-case or use
load instead (which doesn't check for already-loaded libraries).
Limit the version dependencies to user-level features. Don't use newer programming features that aren't available in all the versions you want to support: you'll have to provide a compatibility version for older versions, and it's easier to maintain a single version.
Sometimes you do need a feature in many places, and it's available on all the implementations you care about, but in a different way. This is mostly the case if you want to support both XEmacs and GNU Emacs: they had a frustrating tendency of copying each other's features but not their interface. In this case, defining a compatibility function is more convenient than testing at the point of use.
For example, the following code defines a function that returns the window system of the current frame, the modern GNU way, the modern XEmacs way, and the old-style way when you couldn't combine terminal and GUI frames in the same instance.
((fboundp 'window-system) #'window-system)
(lambda (&optional frame)
(device-type (frame-device frame))))
(lambda (&optional frame) window-system))))
There isn't much code that needs to be platform-dependent. The variable
system-type indicates the operating system. I use it exclusively to activate a few hacks for
ms-dos (yes, my files are that old) and
You may want to add directories to your executable search path (
PATH), but that's usually best done outside Emacs, in your
.profile for Unix-like system and through the control panel in Windows. To test whether an external program is available, call
For code that needs to act differently depending on the type of GUI if any, check
window-type or its successors (see above).
For maximum compatibility, put your code in
~/.emacs. GNU Emacs started looking in
~/emacs.d in version 22. XEmacs started looking in
~/.xemacs in version 21.4. An alternative approach is to put compatibility code in
~/.emacs and finish by loading your main file. Put
(setq load-home-init-file t) somewhere to avoid having recent¹ versions of XEmacs ask you whether you want to move your
.emacs to the XEmacs-only location.
Different versions of Emacs may have different, and incompatible, expansion for some macros. So don't share your byte-compiled files between versions, compile the files on each machine.
Sometimes a feature is deprecated, but you still want to use it because that's all there is in some other version that you want to support. The byte compiler warnings come from the
((not (boundp 'desktop-enable))
(defvaralias 'desktop-enable 'desktop-save-mode))
((get 'desktop-enable 'byte-obsolete-variable)
(put 'desktop-enable 'byte-obsolete-variable nil)))
¹ Relatively speaking, compared with older XEmacs.