AWS CloudShellでEmacsをビルドして使ってみた #reinvent

AWS CloudShellで最新版のEmacsをソースコードからビルドして、永続ストレージであるホームディレクトリ配下にインストールしてみました。リスタートなどでCloudShell環境が初期化されても、最新版のEmacsが引き続き使用可能です。
2021.01.04

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

はじめに

清水です。re:Invent 2020 Werner Vogels Keynoteで発表された新サービスAWS CloudShell、リリース直後の速報エントリでyumコマンドでEmacsがインストールできることを確認しました。

ですがこのyumコマンドでのEmacsインストールには大きく2つの点が問題になる可能性があります。1つはインストール先が永続ストレージであるホームディレクトリではない点です。永続ストレージでないためCloudShellをリスタートしたり、非アクティブな状態が20分続いてセッションがタイムアウトしてしまうと、インストール先のディレクトリが初期化されてしまいます。その都度、yumコマンドでEmacsをインストールする、という方法もあるかもしれませんが、少し手間になりそうですよね。2つ目はyumコマンドでインストールできるEmacsのバージョンが最新ではない点です。先日のエントリで確認するとGNU Emacs 25.3.1ということで極端に古いバージョンではありませんが *1、できれば最新版を使いたいところです。

ということで、AWS CloudShell上のホームディレクトリに現時点(2021/01/04)の最新版となるGNU Emacs 27.1をビルドしてみました。永続ストレージであるホームディレクトリへのインストールとなるので、リスタートやセッションタイムアウトで初期化される心配はありません。 *2 本エントリではこの手順と試行錯誤の記録をまとめてみたいと思います。

AWS CloudShellでEmacsをビルドする

CloudShellでのEmacsのビルドは以下の手順で行いました。 *3

  1. ビルドに必要なパッケージをyumでインストール
  2. 環境変数の追加(.bashrcの編集)
  3. Nettleをソースコードからビルドしてインストール
  4. GnuTLSをソースコードからビルドしてインストール
  5. Emacsをソースコードからビルドしてインストール

ビルドに必要なパッケージをyumでインストール

まずはEmacsとGnuTLS、そしてNettleのビルドに必要なパッケージをyumでinstallしていきます。CloudShell上で以下のコマンドを実行します。 *4

$ sudo yum install -y xz \
                      gcc \
                      m4 \
                      gmp-devel \
                      libtasn1-devel \
                      libunistring-devel \
                      unbound-devel \
                      p11-kit-devel \
                      ncurses-devel \
                      libxml2-devel

環境変数の追加

続いてLD_LIBRARY_PATHPKG_CONFIG_PATHの2つの環境変数を追加し、共有ライブラリ検索時のパスを追加します。今回は.bashrcに記載しています。またこのタイミングでEmacsインストール先となる(バイナリファイルが保存される)$HOME/local/binについてもPATH環境変数に追加しておきます。

この段階でemacsがインストールされていない想定ですので、viコマンドで.bashrcを開きます。

$ vi ~/.bashrc

コマンドモードでjを連打して一番下の行に移動、oで編集モードに入りさらに下に行を追加、 *5 以下をペーストします。

export PATH=$HOME/local/bin:$PATH
export LD_LIBRARY_PATH=$HOME/local/lib64:$HOME/local/lib:/usr/lib64:$LD_LIBRARY_PATH
export PKG_CONFIG_PATH=$HOME/local/lib64/pkgconfig:$HOME/local/lib/pkgconfig:/usr/lib64/pkgconfig:$PKG_CONFIG_PATH

編集が終わったらEscでコマンドモードに戻り、:wqで保存して終了します。(操作でなにかミスなどしたらEscでコマンドモードに戻って:q!で保存せずに終了してやり直します。) *6

保存後、再度ログインするか以下コマンドで.bashrcへの記載内容を有効にします。

$ source ~/.bashrc

Nettleをソースコードからビルドしてインストール

続いてGnuTLSをビルドする際に必要な暗号化ライブラリであるNettleをソースコードからビルドしてインストールします。インストール先はホームディレクトリ内、$HOME/local(実際は/home/cloudshell-user/local)とします。(以降、GnuTLSとEmacsについても同様です。)

作業用にホームディレクトリにbuildというディレクトリを作成し、この配下のnettleディレクトリににソースコードファイルをダウンロード、ビルドを進めていきます。

$ cd
$ mkdir build
$ mkdir build/nettle
$ cd build/nettle/

Nettleのソースコードは以下から、現時点(2021/01/04)の最新バージョン3.6をダウンロードしました。

$ wget https://ftp.gnu.org/gnu/nettle/nettle-3.6.tar.gz

ダウンロードしたら展開します。

$ tar xvf nettle-3.6.tar.gz

展開後に作成されたnettle-3.6ディレクトリに移動します。

$ cd nettle-3.6

./configureを実行しますが、オプションを2つ付与します。1つ目は--enable-mini-gmpで、これをつけておかないと続くGnuTLSのビルド時にエラーが発生してしまいます。 *7 もう一つは--prefix=$HOME/localで、インストール先をホームディレクトリ配下のlocalディレクトリとします。その他のオプションについては./configure --helpで確認が可能です。また実行ログを残しておきたいので、コマンド末尾に2>&1 | tee [ログファイル名]も付与して実行します。

$ ./configure --enable-mini-gmp --prefix=$HOME/local 2>&1 | tee log_configure.log

続いてmakeコマンドを実行します。こちらも実行ログを残しておきたいので、コマンド末尾に2>&1 | tee [ログファイル名]を付与します。

$ make 2>&1 | tee log_make.log

makeコマンドが完了したら、続いてmake installします。こちらも実行ログを残しておきます。

$ make install 2>&1 | tee log_make_install.log

make install完了後、インストール先であるホームディレクトリ配下のlocalディレクトリを確認すると関連するファイルが作成されていることがわかります。

GnuTLSをソースコードからビルドしてインストール

Nettleのビルドとインストールが完了したら、次はGnuTLSをソースコードからビルドしてインストールします。インストール先はNettleと同様、ホームディレクトリ内、$HOME/local(実際は/home/cloudshell-user/local)とします。

作業用のホームディレクトリ配下のbuildディレクトリにgnutlsディレクトリを作成、このディレクトリににソースコードファイルをダウンロード、ビルドを進めていきます。

$ cd
$ mkdir build/gnutls
$ cd build/gnutls/

GnuTLSのソースコードは以下から、現時点(2021/01/02)の最新版のバージョン3.6.15をダウンロードしました。

$ wget https://www.gnupg.org/ftp/gcrypt/gnutls/v3.6/gnutls-3.6.15.tar.xz

ダウンロードしたら展開します。(展開時にyumでインストールしたxzが必要です。)

$ tar xvf gnutls-3.6.15.tar.xz

展開後に作成されたgnutls-3.6.15ディレクトリに移動します。

$ cd gnutls-3.6.15

インストール先をホームディレクトリ配下のlocalディレクトリとするので、--prefix=$HOME/localをつけて./configureを実行します。実行ログを残しておきたいので、コマンド末尾に2>&1 | tee [ログファイル名]も付与します。その他のオプションについては./configure --helpで確認が可能です。

$ ./configure --prefix=$HOME/local 2>&1 | tee log_configure.log

続いてmakeコマンドを実行します。こちらも実行ログを残しておきたいので、コマンド末尾に2>&1 | tee [ログファイル名]を付与します。

$ make 2>&1 | tee log_make.log

makeコマンドが完了したら、続いてmake installします。こちらも実行ログを残しておきます。

$ make install 2>&1 | tee log_make_install.log

make install完了後、インストール先であるホームディレクトリ配下のlocalディレクトリを参照し、関連ファイルが作成されていることを確認しておきます。

Emacsをソースコードからビルドしてインストール

NettleならびにGnuTLSのビルドとインストールが終わったら、いよいよ目的であるEmacsのビルドとインストールを行います。インストール先はNettle、GnuTLSと同様、ホームディレクトリ内の$HOME/local(実際は/home/cloudshell-user/local)とします。そのほか、基本的に手順は他パッケージのビルド&インストールと同様です。まずはビルドするディレクトリを~/build/emacsとして作成します。

$ cd
$ mkdir build/emacs
$ cd build/emacs/

Emacsのソースコードは以下から、現時点(2021/01/04)での最新版である27.1をダウンロードします。

$ wget https://ftp.gnu.org/gnu/emacs/emacs-27.1.tar.xz

ダウンロードしたら展開します。

$ tar xvf emacs-27.1.tar.xz

展開後に作成されたemacs-27.1ディレクトリに移動します。

$ cd emacs-27.1

インストール先をホームディレクトリ配下のlocalディレクトリとするので、--prefix=$HOME/localをつけて./configureを実行します。実行ログを残しておきたいので、コマンド末尾に2>&1 | tee [ログファイル名]も付与します。その他のオプションについては./configure --helpで確認が可能です。

$ ./configure --prefix=$HOME/local 2>&1 | tee log_configure.log

続いてmakeコマンドを実行します。こちらも実行ログを残しておきたいので、コマンド末尾に2>&1 | tee [ログファイル名]を付与します。

$ make 2>&1 | tee log_make.log

makeコマンドが完了したら、続いてmake installします。こちらも実行ログを残しておきます。

$ make install 2>&1 | tee log_make_install.log

make install完了後、インストール先であるホームディレクトリ配下のlocalディレクトリを参照してみます。Emacs関連のファイルが作成されていますね。

すでに~/local/binにはパスを通しているので、任意のディレクトリでemacsコマンドを実行してみます。

最新版のGNU Emacs 27.1が起動しました!

セッションリスタートしてもEmacsが起動できることを確認する

ホームディレクトリに配下に最新のEmacsがインストールできました。続いて、CloudShellをリスタートさせてみて引き続きEmacsが実行できるか確認しておきましょう。右上のActionからRestart AWS CloudShellをクリックします。

確認のダイアログが現れますので、[Restart]でCloudShellをリスタートさせます。

しばらくして、新しいセッションでのCloudShellが実行されました。(プロンプトのIPアドレス部分が変わっていますね。)

emacsコマンドを実行してみます。command not foundにならずEmacsが起動しましたね、やった!

ビルドに使用した不必要なファイルを削除しておく

新しいセッションのCloudShellでも、永続ストレージとなるホームディレクトリにインストールしたEmacsが実行できることが確認できました。片付けとしてビルドに使用したファイルを削除しておきましょう。なおEmacsのビルド、インストール完了した時点でdf -hコマンドを実行すると以下のようになりました。/homeとしては残り51MB、使用率95%となかなかなことになっています。なお、ビルドの試行錯誤の際、Nettleの別バージョンを入れて試していたところ、Emacsのビルド時に空き容量がなくなってディスクフルとなる事象にも遭遇しました。

ビルドは~/buildディレクトリで行っていますので、このディレクトリをまるっと削除してしまいます。

$ rm -fr ~/build

なお、ビルドに使用したファイルを削除した段階で、ホームディレクトリの使用率は20%ほどでした。まぁ実用に耐えれる容量ではないでしょうか。(流石にビルドファイルを残しておくのは現実的でないかと思いますが。)

試行錯誤の記録

以上で最新版のEmacsをCloudShellのホームディレクトリ配下にインストール、セッション再開後も使用する、という目的は果たせました。以下にはいろいろと試行錯誤した記録をまとめておきます。

Emacsだけをソースコードからビルド

実はまず試してみたのは、Emacsだけをソースコードからビルド、他のパッケージ等はyumでインストールしてしまう方法です。xy、gccをyumでインストールした段階で./configureを行うと以下のようにGnuTLSが足りない、ということでエラーとなります。

configure: error: The following required libraries were not found:
     gnutls
Maybe some development libraries/packages are missing?
To build anyway, give:
     --with-gnutls=ifavailable
as options to configure.

ここでgnutls-develもyumでインストールしてしまいます。途中、ncurses-develもyumで追加でインストールすることで、./configuremakemake installまで完了させることができます。

make install直後にEmacsを起動することもできますが、セッションリスタート(つまりホームディレクトリ以外のデータを初期化)したあとにEmacsを起動しようとすると、以下のようにエラーとなってしまいます。

local/bin/emacs: error while loading shared libraries: libgnutls.so.28: cannot open shared object file: No such file or directory

yumでインストールしたGnuTLSまわりのライブラリ、ホームディレクトリ以外の場所(/usr/libなどだったかと思います)に格納されるので、初期化でファイルがなくなってしまった状態、と理解しています。ということで、このGnuTLSについてもソースコードからホームディレクトリ配下にビルドしてインストールすることとしました。

どのパッケージまでソースコードからビルドするか

GnuTLSのソースコードからのビルドの際、当初はNettleもyumでインストールする方法を試していました。しかしGnuTlSの./configure時に以下のようにNettleが見つからない旨エラーが出てしまい、パスを通すなど試してみましたがどうにも解決できませんでした。後述しますが、GMPが必要な点も影響していたかもしれません。

configure: error: 
  ***
  *** Libnettle 3.4.1 was not found.

ちょうどその時、エラーの解決に参照していたサイトが下記なのですが、他ライブラリについてもソースコードからビルドする手順が記載されていたのでyumを使わずNettleに関連するライブラリもソースコードからビルドしようか、ともちょっと思いました。ただ流石にちょと大掛かりになりすぎるかなぁ、となるべくyumを使う方法で試行錯誤していく中で、Nettleまでソースコードからのビルドでおさまった、という具合です。

なお、Nettleについては必要なライブラリ(Libtasn1、Libunistring、p11-kitなどyumでインストールしたもの)がそろっていればビルドはできたかな、という印象です。対してGnuTLSについては./configureでエラーが出て終了してしまうことが多かったかなと。先ほどの「Libnettle 3.4.1 was not found.」のエラーのほか、NettleをインストールしていてもGMPサポートを有効にしていないと(Nettleの./configureの際に--enable-mini-gmpオプションを指定していないと)、以下のようなエラーが出てしまいました。ライブラリ関係のパスをきちんと通していない場合も同様です。

configure: error: 
  ***
  *** Libhogweed (nettle's companion library) 3.4.1 was not found. Note that you must compile nettle with gmp support.
configure: error: 
***
*** gmp was not found.

yumで使われるrpmファイルをローカルにインストール

yumでインストールする際のインストール先を変更できないか、ということも少し調べてみました。yumコマンド自体では難しそうでしたが、インストールする際に使われるrpmパッケージファイルならできそう、とやってみました。

/etc/yum.confを編集して、rpmパッケージを保存しておくようにします。

[cloudshell-user@ip-10-0-100-182 ~]$ cat /etc/yum.conf 
[main]
cachedir=/var/cache/yum/$basearch/$releasever
keepcache=0
debuglevel=2
logfile=/var/log/yum.log
exactarch=1
obsoletes=1
gpgcheck=1
plugins=1
installonly_limit=3
distroverpkg=system-release
timeout=5
retries=7

試しにGnuTLSのrpmパッケージファイル、「gnutls-devel-3.3.29-9.amzn2.x86_64.rpm」をprefix指定してインストールしようとしてみましたが、not relocatableとエラーとなってしまいました。これからrpmファイルをローカル(ホームディレクトリ)にインストールする作戦は断念しました。

[cloudshell-user@ip-10-0-100-182 packages]$ rpm -ivh --prefix=$HOME/local gnutls-devel-3.3.29-9.amzn2.x86_64.rpm 
error: package gnutls-devel is not relocatable
[cloudshell-user@ip-10-0-100-182 packages]$ rpm -qpi gnutls-devel-3.3.29-9.amzn2.x86_64.rpm 
Name        : gnutls-devel
Version     : 3.3.29
Release     : 9.amzn2
Architecture: x86_64
Install Date: (not installed)
Group       : Development/Libraries
Size        : 1502311
License     : GPLv3+ and LGPLv2+
Signature   : RSA/SHA256, Mon 24 Jun 2019 11:07:08 PM UTC, Key ID 11cf1f95c87f5b1a
Source RPM  : gnutls-3.3.29-9.amzn2.src.rpm
Build Date  : Tue 11 Jun 2019 10:57:38 AM UTC
Build Host  : build.amazon.com
Relocations : (not relocatable)
Packager    : Amazon Linux
Vendor      : Amazon Linux
URL         : http://www.gnutls.org/
Summary     : Development files for the gnutls package
Description :
GnuTLS is a secure communications library implementing the SSL, TLS and DTLS
protocols and technologies around them. It provides a simple C language
application programming interface (API) to access the secure communications
protocols as well as APIs to parse and write X.509, PKCS #12, OpenPGP and
other required structures.
This package contains files needed for developing applications with
the GnuTLS library.

まとめ

AWS CloudShell上で最新版のEmacsをソースコードからビルドして、永続ストレージであるホームディレクトリ配下にインストールしてみました。Emacs自体に加え、GnuTLSとさらにNettleをビルドしてインストールしておくこと、また環境変数を追加してライブラリへのパスを通しておくところがミソでした。これでリスタートやセッションタイムアウトでCloudShell環境が初期化されても、最新版のEmacsが引き続き使用可能です。ただしインストール先の永続ストレージについても120日間使用してないと削除される、という制限もありますので、こちらの対策も考えておきたいところだなと思いました。(その都度、ビルドするでも良さそうですが。)いずれにせよ、AWS CloudShell上でのより快適なEmacs環境にまた一歩近付いたかと思います。Happy Hacking with Emacs!

脚注

  1. 例えばmacOS 10.14 MojaveまでデフォルトでインストールされていたEmacsは22.1.1と非常に古いバージョンでした。
  2. ただし、ホームディレクトリの保存期間は最終セッションから120日となっているので、こちらには注意する必要があります。AWS CloudShell のホームディレクトリの保存期間に関する注意事項 #reinvent | Developers.IO
  3. 試行錯誤の末、この手順でビルドができました。
  4. 厳密にはlibxml2-develはyumでインストールしない状態でもEmacsのビルドは可能でした。ただEmacs上でEWWを実行した際にlibxml2まわりエラーが出たため、インストールしています。
  5. viの基本的な操作を記載しているのは個人的な備忘録を兼ねています。いつもi以外で編集モードに入る方法は忘れてしまう……
  6. これも私がviの操作に慣れていないための備忘録です。困ったらとりあえず保存せずに終了してます。
  7. /.configure時に`Libhogweed (nettle's companion library) 3.4.1 was not found. Note that you must compile nettle with gmp support.`となりエラーとなります。