8

Ideally, I would like to be able to store the entire contents of my .emacs.d directory and have it "just work" on any Emacs I load it into, but still take advantage of any features of the specific environment, such as GUI windowing systems.

I'm not looking for an encyclopedia of incompatible features. I'd just like to know how to check for features, OS, version, graphics, etc, and how to take advantage of these without breaking the code for other configurations.

What basic techniques can I use to write Elisp that works in multiple versions of Emacs common in the wild (e.g. 22.x+) and on multiple underlying platforms (e.g. OSX, Linux, Windows, and other *nix), while taking advantage of platform and version-specific features where applicable?

  • 1
    @Malabarba If the question is interpreted as “list all the differences between different versions and platforms”, yes, it's far too broad. But the basic techniques — testing if identifiers are bound, recovering from errors, testing window-system, etc. can be reasonably answered here. – Gilles Oct 9 '14 at 16:15
  • 1
    Emacs 22 is not “recent” anymore. Even Emacs 23 is dated. – lunaryorn Oct 9 '14 at 16:25
  • 1
    I included emacs 22 because it's common in the wild. For example, it's bundled with OSX 10.9. – Paul Miller Oct 9 '14 at 17:27
  • Any serious Emacs user on a Mac is going to download a recent version. Emacs 22 is only included in OS X because it's the last version that was licensed under GPLv2 (and IMO they should just drop it entirely). I think 23+ is much more useful (IIRC Debian stable still uses 23). – shosti Oct 14 '14 at 18:59
  • 2
    Sheesh. The person posing the question asks about Emacs 22+ and you want to "correct" that request to insist on a more recent version? What next - someone asks about forward-char and you want the question changed to ask about scroll-up instead? If the OP wants Emacs 22+ compatibility, leave it alone. And no, the question posed is not only about OS X. And yes, there are still lots of people using older Emacs versions (some even older than 22, FWIW). – Drew Oct 14 '14 at 19:33
8

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.

(if (is-new-feature-available)
    (shiny-new-feature)
  (old-less-nifty-feature))

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 (is-new-feature-available).

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 longlines-mode otherwise.

(global-set-key "\eml" (if (fboundp 'visual-line-mode)
                           'visual-line-mode
                         'longlines-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
(condition-case nil
    (set-specifier default-toolbar-visible-p nil)
  (error nil))
;; For GNU Emacs
(if (fboundp 'tool-bar-mode)
    (tool-bar-mode 0))

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 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.

(cond
 ((fboundp 'window-system)
  (defalias 'compat-window-system 'window-system))
 ((fboundp 'device-type)
  (defun compat-window-system (&optional frame)
    (device-type (frame-device frame))))
 (t
  (defun compat-window-system (&optional frame)
    window-system)))

Environment dependencies

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 windows-nt.

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 executable-find.

For code that needs to act differently depending on the type of GUI if any, check window-type or its successors (see above).

Initialization files

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 byte-obsolete-variable property.

(cond
 ((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.

6

(Community wiki. Please add yours!)

  • If there's a function, added in a newer Emacs version, that you'd like to use, check whether it's defined with fboundp, and define a compatibility function if it's not defined.

    It's considered a bad idea to give the compatibility function the same name as the real function, as other Elisp code may be using the same fboundp trick. Thus, use a prefix for the compatibility function, and use defalias for the same name if it is defined. E.g.:

    (if (fboundp 'propertize)
        (defalias 'my-propertize 'propertize)
      (defun my-propertize (string &rest properties)
        "Return a copy of STRING with text properties added.
    
     [Note: this docstring has been copied from the Emacs 21 version]
    
    First argument is the string to copy.
    Remaining arguments form a sequence of PROPERTY VALUE pairs for text
    properties to add to the result."
        (let ((str (copy-sequence string)))
          (add-text-properties 0 (length str)
                               properties
                               str)
          str)))
    
  • If some piece of configuration should only apply to a certain OS, there are a few different possibilities. You can check the system-type variable, which returns gnu/linux, darwin, windows-nt and a few others (see the docstring).

    You may be tempted to use window-system, though its docstring states that "Use of this variable as a boolean is deprecated", and recommends using display-graphic-p instead. Note that Emacs is able to use different types of displays for different frames nowadays (e.g. one frame on a terminal and another in a "proper" window), so this may cause surprises. Use current-frame-configuration or get-buffer-window-list in your elisp to make the right choice.

  • You may want to check if you are running under the right flavor of emacs. Use featurep to check the variant. E.g.:

    (when (featurep 'xemacs)
       (require 'fsf-compat))
    

    You can also use it to check specific modules are loaded. For instance, if you only use a small and easy to define defun of common-lisp you might opt for defining instead of requiring. E.g.:

    (unless (featurep 'cl)
       (defun caaar (x)
          "Return the `car' of the `car' of the `car' of X."
          (car (car (car x)))))
    
  • Avoid storing .elc files. These are not forward or backward compatible with between some Emacs versions.

0

If you're using functions from cl-lib but don't want to use the deprecated non-namespaced versions, make the cl-lib compatibility library a dependency of your project. That will allow you to use the namespaced cl- functions but retain backward-compatibility.

  • load-library: Cannot open load file: cl-lib — Why would I want cl-lib anyway? What advantage does it have over cl, which has been around since at least 20 years ago? – Gilles Oct 14 '14 at 22:27
  • cl is deprecated and will probably be removed eventually. Using it in code has caused byte-compiler warnings for quite a long time. I think the reason is that they want to reuse some of the cl names (like dolist) with different semantics. – shosti Oct 15 '14 at 0:10

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.