[Top][All Lists]

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Environment variable control in emacs lisp code...

From: Daniel Katz
Subject: Environment variable control in emacs lisp code...
Date: Sat, 18 Feb 2006 12:04:02 -0500
User-agent: Gnus/5.11 (Gnus v5.11) Emacs/22.0.50 (darwin)

I recently found myself writing some elisp code where I needed to set
some environment variables (PATH and JAVA_HOME) locally, call an
external process (e.g. javac), and then reset the variables (for the
sake of other external processes I'd be calling later).  After writing
some code which looked sort of like this:

(let ((old-values (stuff-with (getenv ...))))
  (unwind-protect                       ; Reset no matter what
      (progn                            ; Set the new environment
        (setenv ...)
        (setenv ...)
        (do-a-bunch-of-stuff ...))
    (progn                              ; Reset the old environment
      (setenv ...)                      ; from old-values
      (setenv ...))))

Once I'd finished, I looked at the code and decided that this was
unreadable (especially since do-a-bunch-of-stuff was sort of long) and
that there had to be a better idiom.  I looked around in a bunch of
the elisp code which comes with emacs and found a few places which had
the same sorts of constructions I had, but no examples which really
looked a lot cleaner.  So I decided that I needed a macro to provide
some syntactic abstraction and some automation for this problem, and I
came up with the with-environment-variables macro shown below.

The macro works for all cases that I've tested and seems to do a good
job of making the code a lot easier to read.  The code I had before
reduced to this:

(with-environment-variables ((var1 val1) (var2 val2))
  (do-a-bunch-of-stuff ...))

which seems a lot simpler to me than what I had before.

That said, I'd love any commentary on what I've produced here and on
how to make it better.  In particular, there are several questions
embedded in the comments on how to control indentation, on the
extraction of keys from an alist, any on the use (or not) of the
process-environment variable directly.  Any advice on why things
should be done differently (or explanations of why the way I did them
is the right-and-one-true-way) are appreciated.



;;; with-environment-variables.el --- Environment Manipulation Made Easier

;; Copyright (C) 2006  Dan Katz

;; Author: Dan Katz <address@hidden>
;; Keywords: processes, extensions

;; This file 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 2, or (at your option)
;; any later version.

;; This file is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; GNU General Public License for more details.

;; You should have received a copy of the GNU General Public License
;; along with GNU Emacs; see the file COPYING.  If not, write to
;; the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
;; Boston, MA 02110-1301, USA.

;;; Commentary:

;; When launching an external subprocess (e.g., 'make' or a db
;; client), it can be useful to temporarily set up some environment
;; variables for that subprocess and to then have those variables
;; reset when the subprocess call form finishes (either through a
;; return or through a non-local exit of some sort).  While this can
;; currently be done manually with a combination of getenv, setenv,
;; progn, and unwind-protect, it would be nice to be able to do this
;; in an unobtrusive and syntactically obvious way.  A idiomatically
;; lispish approach would be something like this:
;; (with-environment-variables (("FOO" "bar")
;;                              ("BAZ" (quux quuux)))
;;   (body-depending-on-variables-FOO-and-BAZ)
;;   (another-body-depending-on-variables-FOO-and-BAZ))

;; As an example of when this sort of facility would be useful,
;; consider any case where different versions of a program are
;; installed and can be selected on the basis of an environment
;; variable, and where you might want to select a particular version
;; at runtime.  Well-known examples of such environment variables
;; include JAVA_HOME (for which JDK to use), ANT_HOME (which version
;; of ant you run), ORACLE_HOME and POSTGRESQL_HOME (to help ensure
;; that you get the correct version of the Postgresql database client
;; for the server you intend to query), etc., not to mention the
;; all-time winner of reset variables: PATH.

;;; Code:

(defmacro with-environment-variables (varlist &rest body)
  "Evaluate the BODY forms in an operating system environment
modified by the VARLIST of environment variables to be set, and
return the value of the last form in the BODY.  The environment
will be reset to its original value when the form exits, even if
the exit is nonlocal (e.g., due to errors during the evaluation
of the BODY forms).

VARLIST is a list of variable settings; each element of VARLIST
is a list (\"VARIABLE\" \"VALUE\") of strings.


  (getenv \"BAZ\") => nil
  (with-environment-variables ((\"FOO\" \"BAR\") (\"BAZ\" \"quux\")) 
     (getenv \"BAZ\")) => \"quux\"
  (getenv \"BAZ\") => nil"
  (let ((variable-list (make-symbol "gensym-variable-list")) ; Gensyms
        (original-env  (make-symbol "gensym-original-env")))
    `(let* ((,variable-list ',varlist)                     
             (get-environment (get-keys-from-alist ,variable-list))))
       (unwind-protect  ; Reset the old environment no matter what happens 
             (set-environment ,variable-list) ; Set up the new environment
             ,@body)                          ; and then do stuff
         (set-environment ,original-env)))))

;;; Get code written in the body of a with-environment-variables form
;;; to indent correctly (i.e., as shown in the comments above and as
;;; is normal for other macros of this sort such as 'with-temp-file').
;;; Is this really the right way to register that my macro should have
;;; the indentation I want, or is there a better (i.e., more obvious)
;;; interface for me to use here?
(put 'with-environment-variables 'lisp-indent-function 1)

;;; It seems silly that I'm defining a method to get the keys from an
;;; alist -- I would have thought that this already existed somewhere
;;; in the alist functions.  Granted that it's trivial, but still...
(defun get-keys-from-alist (alist)
  "Given an ALIST, extract the keys into a list."
  (mapcar #'car alist))

;;; These envrironment manipulation functions work correctly, but are
;;; the implementations the right approach?  Maybe just making a
;;; copying and binding of the variable 'process-environment' is a
;;; better approach?  I like the use of a functional interface
;;; (getenv/setenv), but it's not clear to me which approach is more
;;; idiomatic elisp style.
;;; Note that this set of functions works with alists (rather than
;;; hashtables, for instance).  This should be okay since people will
;;; generally only be setting a few (probably fewer than 10)
;;; environment variables at a single time.
(defun get-environment (variables)
  "Given a list of environment VARIABLES, capture the current
values of those VARIABLES into a 'car-cadr' alist."
  (mapcar #'(lambda (variable)
              (list variable (getenv variable)))

(defun set-environment (environment)
  "Given a 'car-cadr' alist of ENVRIONMENT settings, use those
settings to set the current subprocess environment.

Note that the SETTINGS must be in the form of a 'car-cadr'
alist (e.g '((\"foo\" \"bar\") (\"baz\" \"quux\"))')."
  (mapcar #'(lambda (pair)
              (let ((variable (car pair))
                    (value    (cadr pair)))
                (setenv variable value)))

(provide 'with-environment-variables)
;;; with-environment-variables.el ends here

reply via email to

[Prev in Thread] Current Thread [Next in Thread]