macOSのlaunchctlからemacs daemonを動かしたいでござるの巻

# モチベーション

macOSで`launchctl`を使って、起動直後からいい感じにemacs daemonが立ち上がってほしい！

# 環境

-   macOS (11.2.1 arm64)
-   homebrew (3.0.1)
-   Emacs (27.1 x86_64、`brew install --cask emacs`でインストールされるモノ)

# launchdを使う

 `launchd`はmacOSで動いている`systemd`のようなものです。([wikipedia](https://ja.wikipedia.org/wiki/Launchd))
`launchctl`というコマンドがあり、これを使って管理をします。

 せっかくmacOSに備え付けられている機能なので、これを使っていきます。



## 設定ファイルの作成

`launchctl`で扱う設定ファイルの中身はxmlで、拡張子は`plist`です。


ユーザごとの設定は`~/Library/LaunchAgents/`に保存され、ログイン時に自動で読み込まれます。
今回は`gnu.emacs.daemon.plist`というファイル名を使用するため、設定ファイルは`~/Library/LaunchAgents/gnu.emacs.daemon.plist`とします。

ここで、いきなりですが`plist`ファイルの中身です。（[こちら](https://www.emacswiki.org/emacs/EmacsAsDaemon)を参考に作成）

```gnu.emacs.daemon.plist
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
    <dict>
        <key>Label</key>
        <string>gnu.emacs.daemon</string>
        <key>ProgramArguments</key>
        <array>
            <string>/Applications/Emacs.app/Contents/MacOS/Emacs-x86_64-10_14</string>
            <string>--eval</string>
            <string>(cd (concat (getenv "HOME") "/"))</string>
            <string>--fg-daemon</string>
        </array>
        <key>StandardOutPath</key>
        <string>/tmp/gnu.emacs.daemon.stdout.log</string>
        <key>StandardErrorPath</key>
        <string>/tmp/gnu.emacs.daemon.stderr.log</string>
        <key>RunAtLoad</key>
        <true/>
        <key>KeepAlive</key>
        <dict>
            <key>SuccessfulExit</key>
            <false/>
            <key>Crashed</key>
            <true/>
        </dict>
        <key>ServiceDescription</key>
        <string>Gnu Emacs for OSX daemon (x86_64)</string>
    </dict>
</plist>
```

以下、各項目の設定について説明していきます。
詳細なフォーマットについては`man launchd.plist`で確認できます。

### ラベル


```gnu.emacs.daemon.plist
<key>Label</key>
<string>gnu.emacs.daemon</string>
```

見ての通り、サービスの名称です。`launchctl`からstart、stopを行なう際に使用します。


### 実行対象の指定

```gnu.emacs.daemon.plist
<key>ProgramArguments</key>
<array>
    <string>/Applications/Emacs.app/Contents/MacOS/Emacs-x86_64-10_14</string>
    <string>--eval</string>
    <string>(cd (concat (getenv "HOME") "/"))</string>
    <string>--fg-daemon</string>
</array>
```

ここで、実行する対象であるemacsのパス、そして引数を指定します。
以下に各引数について説明していきます。

#### emacsの実行ファイル

   homebrew caskからインストールしたemacsは、rubyスクリプトのラッパー越しに起動しています。
   その際にアーキテクチャも確認するようになっていますが、apple silicon macだとここでハネられてしまい起動しません。
   そこで、

```gnu.emacs.daemon.plist
<string>/Applications/Emacs.app/Contents/MacOS/Emacs-x86_64-10_14</string>
```

と書いて直接emacsのx86バイナリを指定すれば、rosetta2上で何事もなかったかのように動かすことができます。

#### ついでに起動時に`$HOME`にcdしてほしい

```gnu.emacs.daemon.plist
<string>--eval</string>
<string>(cd (concat (getenv "HOME") "/"))</string>
```

このオプションを加えずに実行すると、emacsは`/`ディレクトリで起動してしまい、いちいち移動するのも手間です。そこで`--eval`オプションから続く式で、起動時に`$HOME`へ移動しておくように設定します。

#### さらに、`launchctl start`、`launchctl stop`させたい

   どうやら`--daemon`やら`--bg-daemon`のオプションでは、`launchd`から立ち上がりバックグラウンドに移行するために子プロセスを立ち上げた後、せっかく呼んだ親が死んでしまって`launchctl`から生死がわからなくなってしまいます。
        これを回避するため、`--fg-daemon`を指定しておきます。

### `stderr`、`stdout`の出力先
```gnu.emacs.daemon.plist
<key>StandardOutPath</key>
<string>/tmp/gnu.emacs.daemon.stdout.log</string>
<key>StandardErrorPath</key>
<string>/tmp/gnu.emacs.daemon.stderr.log</string>
```

   `stdout`、`stderr`の出力先を`/tmp`以下に作成したファイルに指定します。
    ログの確認などは指定したこれらのファイルから。

### 読み込みと同時に起動
```gnu.emacs.daemon.plist
<key>RunAtLoad</key>
<true/>
```
   これを`true`とすると`launchd`に読み込みと同時にサービスを立ちあげます。
    `~/Library/LuanchAgents`への配置とあわせれば、ログインと同時にemacs daemonが起動します。

### クラッシュ時などの挙動を設定
```gnu.emacs.daemon.plist
<key>KeepAlive</key>
<dict>
    <key>SuccessfulExit</key>
    <false/>
    <key>Crashed</key>
    <true/>
</dict>
```

   基本的にemacs daemonには動作を維持してほしいので`KeepAlive`を指定、終了時のふるまいを`dict`キーの中で設定していきます。
   `SuccessfulExit`での指定は見ての通り正常終了時の挙動です。今回は`false`としています。
    `Crashed`での指定はクラッシュ時(exit statusがマイナスの時)の挙動です。
    残念ですが、自分の環境だとちょいちょいemacs for osxは落ちてしまうので、自動で復活してもらうために`true`にします。

###  説明
```gnu.emacs.daemon.plist
<key>ServiceDescription</key>
<string>Gnu Emacs for OSX daemon (x86_64)</string>
```
サービスに関する説明です。適当に。


# 環境変数（主に`PATH`）を引き継ぎたい

しかし、これだけだと普段使っているシェルの`PATH`を引き継がないため（path helperによる`PATH`になる？）、emacsから外部コマンドを実行する際にシェル上から呼び出す場合と異なる挙動をしてしまいます。
 そこで、いい感じに環境変数をemacsに渡してくれるpluginがあったため利用しました。

[purcell/exec-path-from-shell: Make Emacs use the $PATH set up by the user's shell](https://github.com/purcell/exec-path-from-shell)


`use-package`を使っているため、以下のように`init.el`などに追加します。
`PATH`以外の環境変数も追加したい場合は上記のリンクを参考に設定しましょう。

```init.el
(use-package exec-path-from-shell
  :ensure t
  :config
  (when (daemonp)
    (exec-path-from-shell-initialize))
)
```

ここではdaemon起動時に`exec-path-from-shell-initialize`を実行し、`PATH`の設定を行ないます。


# `launchctl`コマンドで設定の読み込み
それでは、`~/Library/LaunchAgents/gnu.emacs.daemon.plist`を`launchctl`に読み込ませてみましょう。

    % launchctl load ~/Library/LaunchAgents/gnu.emacs.daemon.plist

`launchctl`のサブコマンドについては[ここ](https://macwiki.osdn.jp/wiki/index.php/%E3%82%B3%E3%83%9E%E3%83%B3%E3%83%89/launchctl#load)なども参考に。

これでemacs daemonが動いていれば`emacsclient -c`などで接続してみましょう。

## `launchctl`からemacs daemonを止める
先ほどのサイトを参考に

    % launchctl stop gnu.emacs.daemon

を実行すれば、emacs daemonを停止させることができます。
もちろんemacs上から`M-x kill-emacs`を実行してもいいですが、`launchctl`から停止させるとemacsが固まってしまったときにも便利です。

また、同様に

    % launchctl start gnu.emacs4.osx.daemon

を実行すれば、止めたemacs daemonを再度開始できます。

## `launchctl`からemacs daemonの状態を確認する

    % launchctl list | grep gnu.emacs.daemon

を実行すると

    74376   0       gnu.emacs.daemon

のような出力が得られます。
これは、左から順に

- 現在の動いているemacs daemonのPID
- 前回のexit status

となります。
もし現在emacs daemonが停止している場合、PIDには`-`と表示されます。
もっと詳細な情報が欲しい場合は、`grep`せず直接ラベルを指定します。

    % launchctl list gnu.emacs.daemon

これによって、以下のような出力が得られます。

```
{
	"StandardOutPath" = "/tmp/gnu.emacs.daemon.stdout.log";
	"LimitLoadToSessionType" = "Aqua";
	"StandardErrorPath" = "/tmp/gnu.emacs.daemon.stderr.log";
	"MachServices" = {
		"org.gnu.Emacs.ServiceProvider" = mach-port-object;
	};
	"Label" = "gnu.emacs4osx.daemon";
	"OnDemand" = true;
	"LastExitStatus" = 0;
	"PID" = 74376;
	"Program" = "/Applications/Emacs.app/Contents/MacOS/Emacs-x86_64-10_14";
	"ProgramArguments" = (
		"/Applications/Emacs.app/Contents/MacOS/Emacs-x86_64-10_14";
		"--eval";
		"(cd (concat (getenv "HOME") "/"))";
		"--fg-daemon";
	);
	"PerJobMachServices" = {
		"com.apple.tsm.portname" = mach-port-object;
		"com.apple.coredrag" = mach-port-object;
		"com.apple.axserver" = mach-port-object;
	};
};

```

# オチ
さて、ここまでいろいろ書いてきましたが、ふと

「Apple Silicon macネイティブで動くemacsはないの？」

と思い調べてみたら[Emacs Plus](https://github.com/d12frosted/homebrew-emacs-plus)というものを発見。

説明に従い`brew tap`して導入すると、なんと`launchctl`用に`plist`ファイルも同梱されておりました……。
現在は、起動時の`$HOME`への`cd`や`KeepAlive`の設定を追加して利用しています。

皆様も良いemacsライフを。

## ほんの少し追記 2021/3/6
先日導入したEmacs Plusですが、添付されているplistファイルで指定するemacsの実行ファイルへのパスが

    /opt/homebrew/opt/emacs-plus@27/bin/emacs

になっていると思います。
しかしGUIでフレーム描画をする場合、macOSのセキュリティ機能により`~/Documents`などのディレクトリへのアクセスが制限されてしまうようです。

そこで、`﻿﻿﻿/opt/homebrew/opt/emacs-plus/Emacs.app`ディレクトリのシンボリックリンクを`/Applications`ディレクトリ下に張り、plistから指定するemacsを

    /Applications/Emacs.app/Contents/MacOS/Emacs

とすれば、`~/Documents`ほかのディレクトリへのアクセスが可能になるようです。
（逆に、iTermなど、macOSからアクセス権を付与したターミナルから`emacs -nw`で起動した場合、制限は発生しないようです。）