4

The Problem

I want to install Emacs on a USB Flash Drive in a portable fashion, that is, Emacs should be able to be run from the UFD, once plugged on a device, without predefined paths to its binaries and save all its persistent data on the UFD.
To obtain this, the Emacs startup script (possibly in the form of an icon for the user to click on) should be able to find the relevant binaries and set the needed directory paths.

Hereafter I take Windows as an example, but my concepts are not platform-specific.

In Windows 90% of the work for a portable Emacs is already done by GNU Emacs, because Emacs for Windows comes as a zipped package, without an installer. According to the docs you have to:

Unzip the zip file preserving the directory structure, and run bin\runemacs.exe. Alternatively, create a desktop shortcut to bin\runemacs.exe, and start Emacs by double-clicking on that shortcut's icon.

Anyway, when extracting on a UFD, persistent data will not be stored on the UFD, but on the home directory of the host system.

This is what I tried

Let us assume that Emacs has been extracted as emacs on the root of the UFD, where there is also a docs folder. In the root I therefore create the following batch script to bootstrap Emacs:

Emacs.cmd
---------
set "HOME=%~dp0docs"
emacs\bin\runemacs.exe   -q   --load emacs\init.el %*

Script Explanation
-q solves side effects due to unwanted reading/writing of init files on the host system.
At the same time, this requires that some init file equivalent is manually loaded with the --load part. An alternative would be using site-start.el, not bypassed by -q.
Note that paths are relative to the position of the script so there is no need to guess the UFD drive letter. The same is not true for the home dir. In this case I use the %~dp0 trick, which is converted by the shell to the script parent dir (somewhat like $(dirname "$0") in Bash).
%* lets add add extra arguments to runemacs, when running Emacs.cmd from the console.
Given -q is intended to make a slimmer Emacs, some default settings might be missing and if you need them you have to manually set them in emacs\init.el. By reading superficially Emacs startup.el code, one such missing item is:

(package-initialize)

which should be added to emacs\init.el or alternatively to site-start.el

This setup works pretty well. So now I come to the actual

Question

In the setup above, where the HOME dir is used to store documents, therefore it would be nice and make sense to have the Emacs settings directory, .emacs.d, outside the document folder.

According to the documentation it should be possible to obtain this result by properly setting the variable user-emacs-directory. Therefore, the following lines should be added to the head of the init file called by the bootstrap script:

init.el 
-------
(setq user-emacs-directory
      (expand-file-name (concat invocation-directory "../.emacs.d")))
(setq abbrev-file-name
      (locate-user-emacs-file "abbrev_defs"))
(setq auto-save-list-file-prefix
      (locate-user-emacs-file "auto-save-list/.saves-"))
(cd "~"))

Despite the -q switch, before running init.el other code is run (this is why the abbreviations and the auto-save directory require a separate setup).

These settings work only partially. In fact, an empty ~/.emacs.d keeps appearing in the home dir.
What library creates the folder? Is it possible to avoid to make the HOME folder dirty?

Debugging startup.el would help, but it is not possible, as it is a fake file. Its actual content is dumped into the Emacs binary.

  • I would try setting HOME before starting Emacs and then at the end of init setting it back. – glucas Aug 7 '16 at 12:21
  • @glucas: To be sure I got it. Do you mean setting a temp HOME in the shell script, acting as the parent for .emacs.d, and the real HOME in my pseudo-init like: (setenv "HOME" (expand-file-name (concat invocation-directory "../../doc")))? – antonio Aug 7 '16 at 12:58
  • Yes, that's what I was thinking. – glucas Aug 7 '16 at 13:53
  • 1
    @glucas: Before posting I read a similar question looking just for a custom user-emacs-directory, not necessarily outside HOME. @phil suggested this idea of a dynamic HOME, but it does not work. Emacs can't find system/config files any more and, when it absolutely needs to write config files, it consequently stalls. Anyway, since I don't see other possiblities, I am determined to investigate this path. ... will let u know` – antonio Aug 7 '16 at 17:04
  • Another idea: can you just use mklink to temporarily mount your emacs init dir to ~/.emacs.d? – glucas Aug 8 '16 at 11:12
2

A solution based on suggestions by @phil and @glucas.

The idea is to set a dynamic HOME variable: first set it in the shell as the parent of .emacs.d; then reset it in my "pseudo-init" to the desired value (here the document folder).
Here is the full working solution.

Solution:

Emacs.cmd
---------
set "HOME=%~dp0\emacs"
emacs\bin\runemacs.exe   -q   --load emacs\init.el %*

init.el 
-------
(setq user-emacs-directory
      (expand-file-name (concat invocation-directory "../.emacs.d")))
(setenv "HOME"
        (expand-file-name (concat invocation-directory "../../docs")))
(setq abbreviated-home-dir (concat 
                    "\\`" 
                (getenv "HOME")
                "\\(/\\|\\'\\)"))
(setq abbrev-file-name
      (locate-user-emacs-file "abbrev_defs"))
(setq auto-save-list-file-prefix
      (locate-user-emacs-file "auto-save-list/.saves-"))
(cd "~"))

Comments

As explained in the question, %~dp0\emacs stands for <script parent dir>\emacs (of course with Windows slashes). emacs is the Emacs install folder and also the designated parent for .emacs.d. Since .emacs.d is supposed to be a subidir of HOME, Emacs will set it automatically.

After starting Emacs and launching my pseudo-init via the relative path --load emacs\init.el, the original value of user-emacs-directory, that is "~/.emacs.d/", is reset to:

(setq user-emacs-directory
      (expand-file-name (concat invocation-directory "../.emacs.d")))

The new path is relative to the Emacs invocation directory and outside HOME. HOME is reset to the document dir:

(setenv "HOME"
        (expand-file-name (concat invocation-directory "../../docs")))

As observed in my question, before starting the pseudo-init, other code is run. Therefore, at this level (locate-user-emacs-file "foo") would report "~\\.emacs.d\\foo". Beside this being not what is desired, "~\\.emacs.d" is not existent. The locate-user-emacs-file function is used by Emacs for reading and writing config files, which would become therefore impossible.

In fact, it turns out that, before starting the init files, Emacs sets abbreviated-home-dir. In our case the variable is set to the old shell value, different from (setenv "HOME" ... above. The wrong abbreviated-home-dir causes (abbreviate-file-name user-emacs-directory) to return the undesired "~/.emacs.d". abbreviated-home-dir is used by locate-user-emacs-file, which ultimately breaks everything.

To fix this misbehaviour, abbreviated-home-dir is reset to the new HOME in

(setq abbreviated-home-dir ...   (getenv "HOME") ...

To understand why the value is encapsulated in those weird strings, check the documentation with C-h v abbreviated-home-dir.

abbrev-file-name and auto-save-list-file-prefix are also set before the pseudo-init call. So they need to be reset to good values.

1

The patch which allows you to specify .emacs.d location via `EMACS_USER_DIRECTORY' environment variable is available in https://debbugs.gnu.org/cgi/bugreport.cgi?bug=15539 but it's not yet merged.

  • 1
    the patch is very nice, the problem is that developers are not willing to merge it. – antonio Nov 14 '16 at 21:47

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.