LoginSignup
2
1

EmacsのAndroidポートのビルド

Last updated at Posted at 2023-10-08

2024-04-07追記
Github Actions上でのビルドに挑戦してきて、やっと最近どうにか満足できるapkをビルドできるようになりました。詳細についてはレポジトリREADME.mdに記載してあります。

EmacsのAndroidポートのビルド

0. 前書き

1. 手順: ビルド用レポジトリを用意手順をさっくり説明
1.1 レポジトリの準備
1.2 forkする
1.3 ローカルにclone
1.4 作業用ブランチのcut
1.5 リモートにpush
1.6 デフォルトブランチの設定

2. ワークフローファイル
2.1 ワークフローファイルのadd
2.2 リモートにpush

3. ビルド: ビルドする手順(詳細略)
3.1 ワークフローをrun

4. ワークフローで行っていることの説明
4.1 ワークフローの定義
4.2 サードパーティライブラリの取得とかpatch適用
4.2.1 tiff
4.2.2 webp
4.2.3 icu
4.2.4 libxml2
4.2.5 gnutls関連
4.2.6 treesitter
4.2.7 harfbuzz
4.2.8 sqlite3
4.2.9 giflib
4.2.10 json
4.2.11 libjpeg
4.2.12 ImageMagick
4.2.13 selinux関連
4.3 ビルドする
4.3.1 レポジトリの作成とモジュールのダウンロード
4.3.2 環境のセットアップとautogen.shの実行
4.3.3 configureの実行
4.3.4 makeとか

5 インストール

0. 前書き

2022年の大晦日にEmacsメンテナのPo LuさんによりAndroidネイティブアプリとしてEmacsを配布するためのブランチfeature/androidが立ち上がりました(過去記事)。その後、emacs-develでの幾ばくかの議論の末に、feature/androidブランチはめでたくmasterブランチへとマージされました。これはどういうことかというと、ユーザー視点に限って言えば今まではfeature/androidブランチを取得してビルドする必要があったのが、今後はmasterブランチ(いずれemacs-30、emacs-31、…となる)を取得してconfigureのオプションで指定すればAndroidポートをビルドできるようになったことを意味しています。さて、このAndroidポートですが主要開発者のPo Luさんによって当初からドキュメントが非常に充実しています。この記事ではそれらのドキュメントにしたがって、2023年10月時点におけるすべての機能を取り込んだEmacs for Androidのビルド手順を説明します。

  • ビルド環境
    Emacsに含まれているAndroidポート用ドキュメントの核となるjava/INSTALL (適当訳、ちょっと訳が古く現時点ではAPI 34が必要です)でも触れられている通り、Android用のソフトウェアはインストールおよび実行されるデバイスではなく別のOS(LinuxとかMacとかWindowsとか)を実行する別のデバイスでビルドされるという特徴があるようです。わたしが所有するAndroid以外の環境としてLinuxはあるものの、これは色々な事をやっている汚れきった環境(その癖仮想化はHW的に不可)なので、例によってGithub Actionsを用いました。

  • ターゲット
    このAndroidポートですが可能な限り古いAndroidバージョンにも対応したいというPo Luさんの意向により、Android 2.2以降であれば対応しているようです(SourceForgeで配布されてます)。当初はGithub Actionsのmatrixを用いて色々なバージョンを作ろうかとも思ったんですが多重度にも制限(256だったか?)があること、自分が所有する実機のバージョンでなければ検証できない、ということでターゲットAPIレベルは22と33に絞りました(33とか持ってませんが最新版くらいはビルドしておこうかなー、と)。

  • サードパーティライブラリ
    java/INSTALLにはjson(Emacs 28より)、treesitterやsqlite(Emacs 29より)といった外部ライブラリをリンクしたバージョンのビルド手順が記載されているので、これらはすべて取り込むことにします(正直自分はselinuxとか「見かけたらすぐに無効化しなさい!」と教えられてきたのでいらないんですけどね)。

  • 共有ユーザーID
    Emacsで著名なmagitといったパッケージを使うには背後にLinux環境が必要になりますが、root化端末でない限りAndroid自体のLinux基盤は使えません。そのためこのAndroidポートではAndroidにおけるLinuxエミュレーションアプリとして実績があるtermuxとの併用についても考慮されています(SourceForgeのREADMEEmacs 30になる予定のマニュアルを参照)。この記事で説明する手順でもTermuxと共存できるように共有ユーザーIDはcom.termuxとしましたが、署名キーが異なるためF-DroidやGoogle PlayからインストールしたTermuxとは共存できません。前述のSourceForge版Termuxをインストールする必要があります(もちろん自分でビルドしてjava/emacs.keystoreで署名したバージョンでも可)。

  • Githubのレポジトリについて
    以降で説明していくわたしのレポジトリにはビルドした結果がartifactsとしてアップロードしてあるのでそれをダウンロード、インストールすればよいのですが、実際にわたしが行っているのは

  1. emacs-mirrorをfork
  2. masterブランチから自分用のブランチ(my/master)をcutしてGithubのデフォルトブランチに設定(わたしはmasterとconflictするような変更をforkで行った際(たとえばconfigure.acにprintf デバッグ仕込むとか)のconflict解決をGithubのweb UIで行うのは余りにもキツいので別ブランチ切ってます)
  3. .github/workflowsにビルド用のYAMLファイルをaddしてGithubのweb UIよりrun

だけなので、上記1.と2.をご自身のgithubアカウントで行い3.の.github/workflowsを持っていけば同じことができます(Githubのweb UIでemacs-mirrorのmasterと自分のforkのmasterをsync、ローカルにmasterをpullしてmy/masterにmerge、pushしてワークフローをrunすれば新鮮なEmacsが随時ビルドできます)。そもそもわたしのレポジトリのActionsはくだらないミスや適当なrunで履歴が汚なすぎるのでおすすめできませぬ。

  • Github
    上述してきた通り、Githubのアカウントお持ちであることを前提とします。ただ、Github Actionsのワークフローファイルで行っているのは、ご覧になれば判ると思いますがLinuxのコマンドプロンプトで行う処理ばかりなので流用は容易いのではないか、と願います(他の環境はよく判りませぬ故)。

1. 手順

1.1 レポジトリの準備

Emacsの本家レポジトリはsavannahのemacs.gitですが、Github外部から直接fork/mirrorする手段は用意されていない(ローカルにcloneしてgithubにpushとかcron組む必要がある)ので、非公式ミラーであるemacs-mirror/emacsをfork元にします。

1.2 forkする

Githubにログインしてemacs-mirror/emacsをforkします(わたしはmy-emacsという名前でforkしましたが、名前は何でも良いと思います)。

1.3 ローカルにclone

gitコマンドでローカルにcloneしてください。

1.4 作業用ブランチのcut

デフォルトブランチはmasterになってる筈なので、そのままgit checkout -b my/master(わたしの場合はmy/masterですが、以下これを例とします)のように自分用master追随ブランチをcutしましょう。

1.5 リモートにpush

ブランチはmy/masterになっていると思いますので、そのブランチをリモートにpushします。git pushするとgit push --set-upstream origin my/masterしろと怒られるのでしたがいましょう。Githubにmy/masterブランチが登録される筈です。

1.6 デフォルトブランチの設定

Githubのワークフローはデフォルトブランチのワークフローを対象とするので、必ず設定してください。
たとえばデフォルトのmasterブランチがデフォルトのままmy/masterブランチにワークフローを追加しても認識してくれない→実行できません。

Githubのweb UIからSettingsDefault Branchmy/emacsに変更します。

2. ワークフローファイル

ローカルのcloneにAndroid版のEmacsをビルドするためのワークフローファイルを追加、commit、pushします。

2.1 ワークフローファイルのadd

cloneしたレポジトリに.github/workflowsというディレクトリーを作成して全部のせビルドワークフローをコピー、gitでaddしてcommit(行末スペースを拒絶するGithubと相性が悪いpatchコードが含まれているので--no-verifyオプションが必要かもしれません)。

2.2 リモートにpush

普通にpushしてください。

3. ビルド

ちょっと記憶が曖昧ですが、もしかしたらGithubのSettingsでActionsを有効にする必要があるかもしれません。

3.1 ワークフローをrun

GithubのActionsrun configure and make Emacsというワークフローがある筈なので、それを選択してRun workflow(ブランチはデフォルトのmy/masterでおk)を実行します。正常に終了すればartifactsとしてEmacs.apk.zipができる筈です(120MBくらい)

4. ワークフローで行っていることの説明

基本的にはjava/INSTALLに記載されたpatchをあてているだけですが、Po Luさんの環境も開発中ということもあり汚れている(素のLinuxではインストールされていないライブラリとかがインストール済みだったり)と思われ、若干の手直しが必要でした。以下順を追って説明します。

4.1 ワークフローの定義

冒頭では名前と起動方法を定義します。重要なのはworkflow_dispatchで、これはこのワークフローがpush等レポジトリに発生するイベントによって自動的に走行するのではなく、GithubのActionsでこのワークフローを選択してRun workflowを選択することにより手動で実行する必要があることを意味しています。

build_emacs_for_android.yml
name: run configure and make Emacs
on:
  workflow_dispatch:

4.2 サードパーティライブラリの取得とかpatch適用

以下、java/INSTALLのpatchと比較しつつ説明していきます(わたしの全部のせビルドワークフローでは試行錯誤しつつトライしてるのでjava/INSTALLの順とは異なりますがget-tiffからget-boringsslまでのjob、download tiffからdownload boringsslのstepとそれぞれの直後にあるexpand archivesのstepの対にたいして、job単位/step対単位であれば並べ替えできます(気持ち悪い人は並べ替えられます; ちなみにわたしも気持ち悪いです)。あとビルドするためにサードパーティライブラリのAndroid.mkを利用しています。このAndroid.mkを使う仕組みはAndroid開発においては古い手法らしく、現在ではAndroid.bpが主流のようです。ということでjava/INSTALLに記載されているように、Android.mkが存在する古いブランチ(nougat-release近辺の模様)を取得しています。

4.2.1 tiff

tiffについてはEmacs for Androidとビルド可能なバージョンがすでに用意されています。

java/INSTALL
libtiff    	- https://sourceforge.net/projects/android-ports-for-gnu-emacs
(Extract and point ``--with-ndk-path'' to tiff-4.5.0-emacs.tar.gz.)

ダウンロードしてartifactsとしてアップロード、

build_emacs_for_android.yml
  get-tiff:
    #
    # get TIFF from Android ports for GNU Emacs
    runs-on: ubuntu-latest
    steps:
    - name: get tiff from Android ports for GNU Emacs
      run: |
        wget https://sourceforge.net/projects/android-ports-for-gnu-emacs/files/tiff-4.5.0-emacs.tar.gz
    # upload as artifacts
    - name: upload source archives
      uses: actions/upload-artifact@v3
      with:
        name: tiff
        path: |
          tiff-4.5.0-emacs.tar.gz

次ジョブでダウンロードしてmy_sub_modulesというディレクトリー配下に解凍しているだけです。

build_emacs_for_android.yml
    # download tiff
    - name: download tiff
      uses: actions/download-artifact@v3
      with:
        name: tiff
        path: my_sub_modules
    - name: expand archives
      run: |
        cd my_sub_modules
        tar xvfz tiff-4.5.0-emacs.tar.gz
        rm -f *gz
        cd -

4.2.2 webp

webpについては本来ならarmv7というデバイス以外はpatchをあてる必要はありません。ただ全部のせする場合にはImageMagickをビルドする際に静的ライブラリが衝突してしまい、ビルドには成功するもののデバイスにインストールするとクラッシュしてしまう(起動できない)ので、モジュール名を弄りました。尚サードパーティライブラリのバージョンについてはImageMagick版webpのv1.0.3の最終コミットを採用しました(ライブラリ衝突の際に「同じバージョン使えばどっちのライブラリでも大丈夫では!?」と思ってこのコミットを選んだんですが、結局駄目で名前変更したのでnougat-releaseでも問題ないと思います)。

java/INSTALL
libwebp	- https://android.googlesource.com/platform/external/webp
(You must apply the patch at the end of this file for the resulting
 binary to work on armv7 devices.)

cloneしてお目当てのコミットをcheckout

build_emacs_for_android.yml
  ##########################
  # Get webp, then upload it
  get-webp:
    runs-on: ubuntu-latest
    steps:
    - name: get webp, patch and upload
      run: |
        git clone https://android.googlesource.com/platform/external/webp
        # use v1.0.3 latest(just before v1.1.0) same as imagemagick version.
        git -C webp checkout a76694a1

以下、ライブラリ名を変えるパッチをあてます。

build_emacs_for_android.yml
diff --git a/Android.mk b/Android.mk
index 8f0cb756..e6268645 100644
--- a/Android.mk
+++ b/Android.mk
@@ -183,16 +183,16 @@ ifeq ($(USE_CPUFEATURES),yes)
   LOCAL_STATIC_LIBRARIES := cpufeatures
 endif
 
-LOCAL_MODULE := webpdecoder_static
+LOCAL_MODULE := webpdecoder_static_webp
 
 include $(BUILD_STATIC_LIBRARY)
 
 ifeq ($(ENABLE_SHARED),1)
 include $(CLEAR_VARS)
 
-LOCAL_WHOLE_STATIC_LIBRARIES := webpdecoder_static
+LOCAL_WHOLE_STATIC_LIBRARIES := webpdecoder_static_webp
 
-LOCAL_MODULE := webpdecoder
+LOCAL_MODULE := webpdecoder_webp
 
 include $(BUILD_SHARED_LIBRARY)
 endif  # ENABLE_SHARED=1
@@ -213,7 +213,7 @@ LOCAL_EXPORT_C_INCLUDES += $(LOCAL_PATH)/src
 # prefer arm over thumb mode for performance gains
 LOCAL_ARM_MODE := arm
 
-LOCAL_WHOLE_STATIC_LIBRARIES := webpdecoder_static
+LOCAL_WHOLE_STATIC_LIBRARIES := webpdecoder_static_webp
 
 LOCAL_MODULE := webp

そしたらartifactsとしてアップロード(委細略)。

build_emacs_for_android.yml
        tar cvfz webp.tar.gz ./webp
    # upload as artifacts
    - name: upload source archives

my_sub_modulesというディレクトリー配下に解凍するだけです(委細略)。

build_emacs_for_android.yml
    # download webp
    - name: download webp

4.2.3 icu

パッチを当てる必要があるとのこと。

java/INSTALL
  icu4c		- https://android.googlesource.com/platform/external/icu/
     (You must apply the patch at the end of this file.)

ダウンロードします(当初は何処らへんのバージョンを使えばよいか探りつつだったのでcloneして探ってましたが、段々判ってきた時点で該当するブランチであろうnougat-releaseのアーカイブをダウンロードしたりしてます)。

build_emacs_for_android.yml
  # Get icu4c, then upload it
  get-icu4c:
    runs-on: ubuntu-latest
    steps:
    - name: get icu4c and upload it
      run: |
        wget https://android.googlesource.com/platform/external/icu/+archive/refs/heads/nougat-release.tar.gz

java/INSTALLのpatchをそのままあてます。ライブラリの名前はそのまま(imagemagick側のicuの名前を変えます)。あてたらアーカイブしてartifactsとしてアップロード、次ジョブでダウンロードして解凍とかは他と同じです(コード略)。

build_emacs_for_android.yml
diff --git a/icu4j/Android.mk b/icu4j/Android.mk
index d1ab3d5..69eff81 100644
--- a/icu4j/Android.mk
+++ b/icu4j/Android.mk
@@ -69,7 +69,7 @@ include $(BUILD_STATIC_JAVA_LIBRARY)
 # Path to the ICU4C data files in the Android device file system:
 icu4c_data := /system/usr/icu
 icu4j_config_root := $(LOCAL_PATH)/main/classes/core/src
-include external/icu/icu4j/adjust_icudt_path.mk
+include $(LOCAL_PATH)/adjust_icudt_path.mk

 include $(CLEAR_VARS)
 LOCAL_SRC_FILES := $(icu4j_src_files)

diff --git a/icu4c/source/common/Android.mk b/icu4c/source/common/Android.mk
index 8e5f757..44bb130 100644
--- a/icu4c/source/common/Android.mk
+++ b/icu4c/source/common/Android.mk
@@ -231,7 +231,7 @@ include $(CLEAR_VARS)
 LOCAL_SRC_FILES += $(src_files)
 LOCAL_C_INCLUDES += $(c_includes) $(optional_android_logging_includes)
 LOCAL_CFLAGS += $(local_cflags) -DPIC -fPIC
-LOCAL_SHARED_LIBRARIES += libdl $(optional_android_logging_libraries)
+LOCAL_SHARED_LIBRARIES += libdl libstdc++ $(optional_android_logging_libraries)
 LOCAL_MODULE_TAGS := optional
 LOCAL_MODULE := libicuuc
         LOCAL_RTTI_FLAG := -frtti

4.2.4 libxml2

patchをあてる前にコミットedb5870767fed8712a9b77ef34097209b61ab2db'をrevertするよう記されています。

java/INSTALL
libxml2	- https://android.googlesource.com/platform/external/libxml2/
(You must also place the dependency icu4c in ``--with-ndk-path'',
 and apply the patch at the end of this file.)
 ...
 This patch must be applied to the Android.mk in Google's version of
libxml2 before it can be built for Emacs.  In addition, you must also
revert the commit `edb5870767fed8712a9b77ef34097209b61ab2db'.

cloneしてrevertします。ちなみにrevertするのはHTMLサポートを無効にしたコミットとのことです。

build_emacs_for_android.yml
  # Get libxml2 and upload it
  get-libxml2:
    runs-on: ubuntu-latest
    steps:
    # Clone libxml2 and upload it
    - name: clone libxml2 and upload as tar.gz
      run: |
        git clone https://android.googlesource.com/platform/external/libxml2/
        git -C libxml2 checkout nougat-release
        git -C libxml2 config user.email "you@example.com"
        git -C libxml2 config user.name "Your Name"
        git -C libxml2 revert edb5870767fed8712a9b77ef34097209b61ab2db

ここではjava/INSTALLのpatchに加えて、更にLOCAL_EXPORT_CFLAGS := -isystem $(LOCAL_PATH)/includeという行を追加しています。C言語におけるヘッダのincludeでは#include <aaa.h>#include "bbb.h"という書き方があります。この<aaa.h>で指定されているヘッダファイルはシステムのインクルードファイル用ディレクトリーからヘッダを読み込むよう指定する書き方で、元々ソースに記述されているLOCAL_C_INCLUDESでは指定できないようです。java/INSTALLを手がかりに、どうやら-isystem path/to/system_include_dirという情報をコンパイラに渡せばヘッダを見つけてくれる事が判りましたが、ここでコンパイルしているlibxml2はicu4cの56、imagemagickのlibxml2は64というバージョンのicu4cを使う別バージョンだったため、configer ANDROID_CFLAGS=-isystem path/to/system_include_dirとしてしまうと、どちらかで実行時エラーを起こしてクラッシュするバージョンがビルドされてしまいます。結局cross/ndk-build/READMEからcross/ndk-build/ndk-build-static-library.mkと辿って、モジュールのAndroid.mkでLOCAL_EXPORT_CFLAGSを設定すれば、モジュール毎にコンパイラにCFLAGを渡せる事が判ったので、正にそれを行っています。

build_emacs_for_android.yml
diff --git a/Android.mk b/Android.mk
index 6ec80122..3c0c2e13 100644
--- a/Android.mk
+++ b/Android.mk
@@ -63,6 +63,7 @@ include $(CLEAR_VARS)
 LOCAL_SRC_FILES := $(common_SRC_FILES)
 LOCAL_C_INCLUDES += $(common_C_INCLUDES)
 LOCAL_CFLAGS += $(common_CFLAGS) -fvisibility=hidden
+LOCAL_EXPORT_CFLAGS := -isystem $(LOCAL_PATH)/include
 LOCAL_SHARED_LIBRARIES += libicuuc
 LOCAL_MODULE := libxml2
 LOCAL_CLANG := true
@@ -76,10 +77,12 @@ include $(CLEAR_VARS)
 LOCAL_SRC_FILES := $(common_SRC_FILES)
 LOCAL_C_INCLUDES := $(common_C_INCLUDES)
 LOCAL_CFLAGS += $(common_CFLAGS)
+LOCAL_EXPORT_CFLAGS := -isystem $(LOCAL_PATH)/include
 LOCAL_SHARED_LIBRARIES := libicuuc
 LOCAL_MODULE:= libxml2
 LOCAL_CLANG := true
 LOCAL_ADDITIONAL_DEPENDENCIES += $(LOCAL_PATH)/Android.mk
+LOCAL_EXPORT_C_INCLUDES += $(LOCAL_PATH)
 include $(BUILD_SHARED_LIBRARY)
 
 # For the host
@@ -89,8 +92,11 @@ include $(CLEAR_VARS)
 LOCAL_SRC_FILES := $(common_SRC_FILES)
 LOCAL_C_INCLUDES += $(common_C_INCLUDES)
 LOCAL_CFLAGS += $(common_CFLAGS) -fvisibility=hidden
+LOCAL_EXPORT_CFLAGS := -isystem $(LOCAL_PATH)/include
 LOCAL_SHARED_LIBRARIES += libicuuc-host
 LOCAL_MODULE := libxml2
 LOCAL_CLANG := true
 LOCAL_ADDITIONAL_DEPENDENCIES += $(LOCAL_PATH)/Android.mk
 include $(BUILD_HOST_STATIC_LIBRARY)
+
+$(call import-module,libicuuc)

アーカイブしてartifactsとしてアップロード、次ジョブでダウンロードと解凍は他と一緒なので割愛します。

4.2.5 gnutls関連

java/INSTALLの以下の部分です。バージョンは限定されるようですが、わたしの実機であるAndroid 8.0のarm(aarch64とも呼ぶらしい)については依存関係も含めてすべてEmacs for Androidとビルド可能なバージョンがすでに用意されています。get-gnutls-and-dependenciesというジョブで取得してます。コードは割愛。

java/INSTALL
Modified copies of GnuTLS and its dependencies (such as libgmp,
libtasn1, p11-kit) which can be built with the ndk-build system can be
found at https://sourceforge.net/projects/android-ports-for-gnu-emacs.

They have only been tested on arm64 Android systems running Android
5.0 or later, and armv7l systems running Android 13 or later, so your
mileage may vary, especially if you are trying to build Emacs for
another kind of machine.

To build Emacs with GnuTLS, you must unpack each of the following tar
archives in that site:

  gmp-6.2.1-emacs.tgz
  gnutls-3.7.8-emacs.tar.gz
  libtasn1-4.19.0-emacs.tar.gz
  p11-kit-0.24.1-emacs.tar.gz
  nettle-3.8-emacs.tar.gz

4.2.6 treesitter

これも対応版が用意されてました。ダウンロードとかの処理はジョブget-treesiterで行っています

java/INSTALL
A copy of tree-sitter modified to build with the ndk-build system can
also be found that URL.  To build Emacs with tree-sitter, you must
unpack the following tar archive in that site:

  tree-sitter-0.20.7-emacs.tar.gz

4.2.7 harfbuzz

。これも対応版が用意されてました。ジョブはget-harfbuzzです。

java/INSTALL
A copy of HarfBuzz modified to build with the ndk-build system can
also be found at that URL.  To build Emacs with HarfBuzz, you must
unpack the following tar archive in that site:

  harfbuzz-7.1.0-emacs.tar.gz

4.2.8 sqlite3

モジュールのサブディレクトリーdistをconfigureのオプションで指定せよとのこと。これはダウンロード、artifacts経由で渡した次ジョブで行います。

java/INSTALL
sqlite3	- https://android.googlesource.com/platform/external/sqlite/
(You must apply the patch at the end of this file, and add the `dist'
 directory to ``--with-ndk-path''.)

cloneしてコミットハッシュf63e8d96e298783c310c08030d4c51a875dae4cdをチェックアウトします。コミットはパッチにあったLOCAL_SDK_VERSION := 23という記述から探しました(多分nougat-releaseでも大丈夫かなあ)。

build_emacs_for_android.yml
  # Get sqlite3 and upload it
  get-sqlite3:
    runs-on: ubuntu-latest
    steps:
    - name: clone sqlite3 and upload as tar.gz
      run: |
        git clone https://android.googlesource.com/platform/external/sqlite
        # commit hash had guessed by `LOCAL_SDK_VERSION := 23'
        git -C sqlite checkout f63e8d96e298783c310c08030d4c51a875dae4cd

java/INSTALLのパッチをそのままあてます。

build_emacs_for_android.yml
diff --git a/dist/Android.mk b/dist/Android.mk
index bf277d2..36734d9 100644
--- a/dist/Android.mk
+++ b/dist/Android.mk
@@ -141,6 +141,7 @@ include $(BUILD_HOST_EXECUTABLE)
 include $(CLEAR_VARS)
 LOCAL_SRC_FILES := $(common_src_files)
 LOCAL_CFLAGS += $(minimal_sqlite_flags)
+LOCAL_EXPORT_C_INCLUDES += $(LOCAL_PATH)
 LOCAL_MODULE:= libsqlite_static_minimal
 LOCAL_SDK_VERSION := 23
 include $(BUILD_STATIC_LIBRARY)

diff --git a/dist/sqlite3.c b/dist/sqlite3.c
index b0536a4..8fa1ee9 100644
--- a/dist/sqlite3.c
+++ b/dist/sqlite3.c
@@ -26474,7 +26474,7 @@ SQLITE_PRIVATE const char *sqlite3OpcodeName(int i){
 */
 #if !defined(HAVE_POSIX_FALLOCATE) \
       && (_XOPEN_SOURCE >= 600 || _POSIX_C_SOURCE >= 200112L)
-# define HAVE_POSIX_FALLOCATE 1
+/* # define HAVE_POSIX_FALLOCATE 1 */
 #endif

 /*

4.2.9 giflib

ジョブはget-giflibです。java/INSTALLにはAndroid.mkにLOCAL_EXPORT_CFLAGS := -I$(LOCAL_PATH)を追加するよう記述されています。

java/INSTALL
giflib	- https://android.googlesource.com/platform/external/giflib
(You must add LOCAL_EXPORT_CFLAGS := -I$(LOCAL_PATH) before
 its Android.mk includes $(BUILD_STATIC_LIBRARY))

これもパッチで行いました。

build_emacs_for_android.yml
diff --git a/Android.mk b/Android.mk
index 03a9355..13a93d0 100644
--- a/Android.mk
+++ b/Android.mk
@@ -14,4 +14,5 @@ LOCAL_SRC_FILES := \
 LOCAL_CFLAGS += -Wno-format -Wno-sign-compare -Wno-unused-parameter -DHAVE_CONFIG_H
 LOCAL_MODULE:= libgif

+LOCAL_EXPORT_C_INCLUDE_DIRS := $(LOCAL_PATH)

4.2.10 json

get-janssonというジョブです。java/INSTALLによるとAndroid.mkにLOCAL_EXPORT_INCLUDES := $(LOCAL_C_INCLUDES)を追加して、android/jansson_config.handroid/jansson_private_config.hにコピーするとあります。

java/INSTALL
libjansson	- https://github.com/akheron/jansson
(You must add LOCAL_EXPORT_INCLUDES := $(LOCAL_C_INCLUDES) before
 its Android.mk includes $(BUILD_SHARED_LIBRARY), then copy
 android/jansson_config.h to android/jansson_private_config.h.)

今回もコピーを含めてパッチで処理しましたが、それだけだとヘッダが見つからずmakeでこけてしまったので、ヘッダのディレクトリー指定を追加しました(LOCAL_EXPORT_INCLUDESLOCAL_EXPORT_CFLAGS)。

build_emacs_for_android.yml
diff --git a/Android.mk b/Android.mk
index e3b09e7..c0aa047 100644
--- a/Android.mk
+++ b/Android.mk
@@ -27,4 +27,6 @@ LOCAL_CFLAGS += -O3 -DHAVE_STDINT_H=1
 
 LOCAL_MODULE:= libjansson
 
+LOCAL_EXPORT_INCLUDES := $(LOCAL_C_INCLUDES)
+LOCAL_EXPORT_CFLAGS := -isystem $(LOCAL_PATH)/src -I $(LOCAL_PATH)/android
 include $(BUILD_SHARED_LIBRARY)
diff --git a/android/jansson_private_config.h b/android/jansson_private_config.h
new file mode 100644
index 0000000..618a0da
--- /dev/null
+++ b/android/jansson_private_config.h
@@ -0,0 +1,43 @@
+/*
+ * Copyright (c) 2010-2016 Petri Lehtinen <petri@digip.org>
+ *
+ * Jansson is free software; you can redistribute it and/or modify
+ * it under the terms of the MIT license. See LICENSE for details.
+ *
+ *
+ * This file specifies a part of the site-specific configuration for
+ * Jansson, namely those things that affect the public API in
+ * jansson.h.
+ *
+ * The configure script copies this file to jansson_config.h and
+ * replaces @var@ substitutions by values that fit your system. If you
+ * cannot run the configure script, you can do the value substitution
+ * by hand.
+ */
+
+#ifndef JANSSON_CONFIG_H
+#define JANSSON_CONFIG_H
+
+/* If your compiler supports the inline keyword in C, JSON_INLINE is
+   defined to `inline', otherwise empty. In C++, the inline is always
+   supported. */
+#ifdef __cplusplus
+#define JSON_INLINE inline
+#else
+#define JSON_INLINE inline
+#endif
+
+/* If your compiler supports the `long long` type and the strtoll()
+   library function, JSON_INTEGER_IS_LONG_LONG is defined to 1,
+   otherwise to 0. */
+#define JSON_INTEGER_IS_LONG_LONG 1
+
+/* If locale.h and localeconv() are available, define to 1,
+   otherwise to 0. */
+#define JSON_HAVE_LOCALECONV 0
+
+/* Maximum recursion depth for parsing JSON input.
+   This limits the depth of e.g. array-within-array constructions. */
+#define JSON_PARSER_MAX_DEPTH 2048
+
+#endif

4.2.11 libjpeg

ジョブはget-libjpegです。java/INSTALLにはLOCAL_EXPORT_C_INCLUDE_DIRS := $(LOCAL_PATH)を追加するよう記載されています。

java/INSTALL
libjpeg-turbo - https://android.googlesource.com/platform/external/libjpeg-turbo
(You must add LOCAL_EXPORT_C_INCLUDE_DIRS := $(LOCAL_PATH) before
its Android.mk includes $(BUILD_SHARED_LIBRARY))

その通りパッチを当てました。

build_emacs_for_android.yml
 get-libjpeg:
   runs-on: ubuntu-latest
   steps:
   - name: get libjpeg-turbo and patch it
     run: |
       git clone https://android.googlesource.com/platform/external/libjpeg-turbo
       git -C libjpeg-turbo checkout nougat-release
       patch --directory=libjpeg-turbo -p1<<'EOS'
       diff --git a/Android.mk b/Android.mk
       index 2801805f..ab94028e 100644
       --- a/Android.mk
       +++ b/Android.mk
       @@ -75,6 +75,9 @@ ifneq (,$(TARGET_BUILD_APPS))
         LOCAL_SDK_VERSION := 17
        endif
        
       +# Added to build with GNU Emacs on Android
       +LOCAL_EXPORT_C_INCLUDE_DIRS := $(LOCAL_PATH)
       +
        # Build as a static library.
        LOCAL_MODULE := libjpeg_static
        include $(BUILD_STATIC_LIBRARY)
       EOS
       tar cvfz libjpeg-turbo.tar.gz ./libjpeg-turbo

4.2.12 ImageMagick

次はImageMagickです。今まで追加してきたライブラリの独自バージョンがいくつか含まれている大物です。まずはjava/INSTALLの記載より。

java/INSTALL
There is a third party port of ImageMagick to Android.  Unfortunately,
the port also uses its own patched versions of libpng, libjpeg,
libtiff and libwebp, which conflict with those used by Emacs.  Its
Makefiles were also written for MS Windows, so you must also apply the
patch at the end of this file.

つまりパッチを沢山当てる必要があるとのこと。更にlibxml2とともにビルドする場合には異なるバージョンで同じファイル名をもつ静的ライブラリの衝突を回避するためのパッチの追加も必要でした。でかいので全部は載せません。java/INSTALLのパッチに加えて、以下のように名前を弄っています。ジョブはget-imagemagickです。

build_emacs_for_android.yml
-LOCAL_STATIC_LIBRARIES += libicuuc
+LOCAL_STATIC_LIBRARIES += libicuucmagick 

4.2.13 selinux関連

最後はselinuxとその依存関係です。

java/INSTALL
libselinux	- https://android.googlesource.com/platform/external/libselinux
(You must apply the patches at the end of the file, and obtain
 the following three dependencies.)
libpackagelistparser
    https://android.googlesource.com/platform/system/core/+/refs/heads/nougat-mr1-dev/libpackagelistparser/
libpcre	- https://android.googlesource.com/platform/external/pcre
libcrypto	- https://android.googlesource.com/platform/external/boringssl
(You must apply the patch at the end of this file when building for
 ARM systems.)

まずはselinuxから。

build_emacs_for_android.yml
  get-libselinux:
    runs-on: ubuntu-latest
    steps:
    - name: get selinux and archive it
      run: |
        git clone https://android.googlesource.com/platform/external/libselinux
        git -C libselinux checkout nougat-release
        patch --directory libselinux -p1 << 'EOS'
        diff --git a/Android.mk b/Android.mk
      # ...
      # 以下 java/INSTALLのパッチをそのまま当ててます

次にAndroidのcoreライブラリに含まれているpackagelistparserです。ブランチは指定通りnougat-mr1-devをチェックアウトします。

build_emacs_for_android.yml
  get-core:
    runs-on: ubuntu-latest
    steps:
    - name: get core and archive it
      run: |
        git clone https://android.googlesource.com/platform/system/core
        git -C core checkout nougat-mr1-dev

システムヘッダファイルをmakeが見つけられなかったので、LOCAL_EXPORT_CFLAGS := -isystem $(LOCAL_PATH)/../includeを追加するパッチを当てます。

build_emacs_for_android.yml
diff --git a/libpackagelistparser/Android.mk b/libpackagelistparser/Android.mk
index c8be050e0..563fdc66a 100644
--- a/libpackagelistparser/Android.mk
+++ b/libpackagelistparser/Android.mk
@@ -13,6 +13,7 @@ LOCAL_EXPORT_C_INCLUDE_DIRS := $(LOCAL_PATH)/include
 LOCAL_CLANG := true
 LOCAL_SANITIZE := integer
 
+LOCAL_EXPORT_CFLAGS := -isystem $(LOCAL_PATH)/../include
 include $(BUILD_SHARED_LIBRARY)
 
 #########################
@@ -29,4 +30,5 @@ LOCAL_EXPORT_C_INCLUDE_DIRS := $(LOCAL_PATH)/include
 LOCAL_CLANG := true
 LOCAL_SANITIZE := integer
 
+LOCAL_EXPORT_CFLAGS := -isystem $(LOCAL_PATH)/../include
 include $(BUILD_STATIC_LIBRARY)

次はpcreです。cloneしてnougat-releaseをチェックアウトするだけです。

build_emacs_for_android.yml
  # get pcre from google
  get-pcre:
    runs-on: ubuntu-latest
    steps:
    - name: get pcre and archive it
      run: |
        git clone https://android.googlesource.com/platform/external/pcre
        git -C pcre checkout nougat-release

最後はboringsslです。cloneしてnougat-releaseをチェックアウト、artifacts経由で次ジョブに渡して

build_emacs_for_android.yml
  # get boringssl from google
  get-boringssl:
    runs-on: ubuntu-latest
    steps:
    - name: get boringssl and archive it
      run: |
        git clone https://android.googlesource.com/platform/external/boringssl
        git -C boringssl checkout nougat-release

次ジョブにてターゲットがarmならjava/INSTALLのパッチをそのまま当てます。

build_emacs_for_android.yml
    # download boringssl
    - name: download boringssl
      uses: actions/download-artifact@v3
      with:
        name: boringssl
        path: my_sub_modules
    - name: expand archives
      run: |
        cd my_sub_modules
        tar xvfz boringssl.tar.gz
        # patch if ARM
        if [ ${{ matrix.abi }} == "arm" ]
        then
          patch --directory boringssl -p1 << 'EOS'
        # ...
	# コードは省略します

4.3 ビルドする

4.2 サードパーティライブラリの取得とかpatch適用が終わったらそれらと一緒にEmacsをビルドします。needs:に記した前提条件は、正常終了が実行の条件となるジョブであり、それらのジョブというのがすなわち4.2 サードパーティライブラリの取得とかpatch適用ということです。

strategymatrixですが、これらはビルドする環境や配布先のデバイスを指定します。当初は「一片にビルド走って壮観だろうなあ」とか思って書きましたが、200以上のジョブの赤い異常終了ステータスを目にするのはあまり面白くなく、自分がもっている環境じゃないとテストもできないので、実績2つのmatrixだけです。

build_emacs_for_android.yml
  job1:
    runs-on: ubuntu-latest
    needs: [get-libselinux, get-core, get-pcre, get-boringssl, get-tiff, get-webp, get-libxml2, get-icu4c, get-gnutls-and-dependencies, get-treesiter, get-harfbuzz, get-sqlite3, get-giflib, get-jansson, get-libjpeg, get-imagemagick]
    strategy:
      matrix:
        api-version: [34]
        # ndk-version: [23.2.8568313, 24.0.8215888, 25.2.9519653]
        ndk-version: [25.2.9519653]
        # abi: [i686, x86_64, aarch64, armv7a, mips64, mips, arm]
        abi: [aarch64]
        # minsdk: [22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33]
        minsdk: [22, 33]
  • api-version:Android SDK同梱の`android.jar'のパス。api levelとも呼ばれるもので、最新バージョンの指定が必須。先頃33から34に更新されました。

  • ndk-version: ビルドしたEmacsを実行するCPUに応じたAndroid NDKのCコンパイラーのパス。古かったりバージョンが違ったりで色々問題が発生するらしいです。

  • abi: ビルドしてインストール先となるCPU。configureとかパッチではaarch64がarm64-v8aとかになってます。対応はconfigure.acに書いてあります。

configure.ac
  AC_MSG_CHECKING([for the kind of Android system Emacs is being built for])
  cc_target=`${ANDROID_CC} -v 2>&1 | sed -n 's/Target: //p'`
  case "$cc_target" in
[
    *i[3-6]86*) android_abi=x86
      ;;
    *x86_64*) android_abi=x86_64
      ;;
    *aarch64*) android_abi=arm64-v8a
      ;;
    *arm*v7a*) android_abi=armeabi-v7a
      ;;
    *mips64*) android_abi=mips64
      ;;
    *mips*) android_abi=mips
      ;;
    *arm*) android_abi=armeabi
      ;;
]
  • minsdk: アプリが実行できるAndroidの最低バージョン。ユーザーにとってはいちばん気のなるところだと思います(たとえばわたしのAndroid 8.0端末では先だってのslackのバージョンアップにより対象バージョンから漏れて、アプリによるアクセスができなくなりました)。

4.3.1 レポジトリの作成とモジュールのダウンロード

レポジトリ自体をチェックアウトしてからartifactsとしてモジュールをダウンロードするためのディレクトリーを作成します。最初はレポジトリのルートにそのままダウンロードしてたんですが、core/libpackagelistparserのビルドが何をどーやって指定しても上手くゆかず、ログをひっくり返してやっとEmacs自体のビルド処理がレポジトリルートのcoreというファイルを削除しているらしい事がわかったので、別途フォルダを作成するよう変更しました。このフォルダに4.2 サードパーティライブラリの取得とかpatch適用のモジュール達をダウンロードして展開します。

build_emacs_for_android.yml
    steps:
    #
    # Checkout source.
    - name: Checkout source
      uses: actions/checkout@v3
    #
    # make directory for sub modules
    - name: make directory for sub modules
      run: |
        mkdir my_sub_modules

4.3.2 環境のセットアップとautogen.shの実行

どこかで見つけた例の通りにjavaとandroidの環境をセットアップして、Emacsをレポジトリから直接ビルドする際に通常実行するautogen.shを実行します(これによってconfigureスクリプトが作成される)。

build_emacs_for_android.yml
    # Setup java/jdk.
    - name: Setup java environment
      uses: actions/setup-java@v3
      with:
        distribution: zulu
        java-version: 11
    #
    # Setup android environment.
    - name: Setup android
      uses: android-actions/setup-android@v2
    #
    # Run autogen.sh
    - name: run autogen.sh
      run: |
        ./autogen.sh

4.3.3 configureの実行

やっとconfigureを実行します。モジュールを指定したり通常のEmacsのconfigure時のように有効にするライブラリとかを指定してます。configureの前後にあるsh -x2>&1 | tee configure.outは上手く行かなかったとき用のログ収集のため。sh -xを削除すれば出力はぐっと減ります。

指定しているオプションについて説明すると

  • --with-android: matrixのapi-version、すなわち34です
  • ANDROID_CC: matrixのndk-versionからCPUがmatrixのabi、対象とする最低バージョンのAndroid APIレベルがmatrixのminsdkであることを指定しています。
  • SDK_BUILD_TOOLS: これはapi-versionと同じにしてます(定数になってますが意味はありません)。
  • --with-ndk-path: artifacts経由でダウンロード、解凍したモジュールのAndroid.mkがあるディレクトリーです。
  • --with-shared-user: Termuxと互いにディレクトリーを参照できるようにcom.termuxを指定しています。

Google PlayやF-DroidのTermuxはそれぞれその配布元のキーで署名されているので、それらがインストールされている場合には、共有ユーザーIDにcom.termuxを指定するとインストールができません(不明なキーで署名された同一ユーザー名を騙るアプリとして認識されるからです)。ここで共存先として想定しているのは、Emacs for Androidで配布されているバージョンのTermuxです。Termuxもオープンソースであり、Githubにレポジトリもあるのでforkして署名だけjava/emacs.keystoreを使って自分でビルドすることもできます。他のプラグイン(Termux:bootとか)も使いたい人は自前でビルドする方がよいかもしれません。

  • その他の--with-XXX: Emacsのいつものビルドで指定するオプションと同じ。--with-ndk-pathで指定したモジュールについてはyesを指定しましょう(--with-x-toolkit=noは不要かもしれません; 最小構成で実験的にビルドしてたときの名残りです)。
build_emacs_for_android.yml
        MODULE_ROOT=$(pwd)/my_sub_modules
        sh -x ./configure \
          --with-android=$ANDROID_HOME/platforms/android-${{ matrix.api-version }}/android.jar \
          ANDROID_CC="$ANDROID_HOME/ndk/${{ matrix.ndk-version }}/toolchains/llvm/prebuilt/linux-x86_64/bin/${{ matrix.abi }}-linux-android${{ matrix.minsdk }}-clang" \
          SDK_BUILD_TOOLS=$ANDROID_HOME/build-tools/34.0.0 \
          "--with-ndk-path=$MODULE_ROOT/libselinux $MODULE_ROOT/core/libpackagelistparser $MODULE_ROOT/pcre $MODULE_ROOT/boringssl $MODULE_ROOT/tiff-4.5.0 $MODULE_ROOT/webp $MODULE_ROOT/libxml2 $MODULE_ROOT/icu $MODULE_ROOT/gmp-6.2.1 $MODULE_ROOT/gnutls-3.7.8 $MODULE_ROOT/libtasn1-4.19.0 $MODULE_ROOT/nettle-3.8 $MODULE_ROOT/p11-kit-0.24.1 $MODULE_ROOT/tree-sitter-0.20.7 $MODULE_ROOT/harfbuzz-7.1.0 $MODULE_ROOT/sqlite/dist $MODULE_ROOT/giflib $MODULE_ROOT/jansson $MODULE_ROOT/libjpeg-turbo $MODULE_ROOT/Android-ImageMagick7"  \
        --with-x-toolkit=no \
        --with-shared-user-id=com.termux \
        --with-gnutls=yes \
        --with-tree-sitter=yes \
        --with-harfbuzz=yes \
        --with-sqlite3=yes \
        --with-xml2=yes \
        --with-gif=yes \
        --with-json=yes \
        --with-tiff=yes \
        --with-jpeg=yes \
        --with-webp=yes \
        --with-png=yes \
        --with-imagemagick=yes \
        --with-selinux=yes 2>&1 | tee configure.out

4.3.4 makeとか

makeして成功したら*.apkをartifactsとしてアップロードします。makeのオプションV=1はデバッグ用に沢山出力させるたねのオプションかな? -j$(nproc)は多重度を上げて高速化するためのオプション。とるとスピードは遅くなるが出力は読みやすくなります。

build_emacs_for_android.yml
    #
    # Run make to build apk.
    - name: run make
      run: |
        make V=1 -j$(nproc) ||
          for F in cross/ndk-build/*.a
          do
            echo "ayatakesi_debug: nm $F"
            nm $F
            echo
          done
    #
    # Upload apk as artifacts.
    - name: upload apk
      uses: actions/upload-artifact@v3
      with:
        name: Emacs.apk
        path: ./java/*.apk

5 インストール

ダウンロードして解凍して*.apkをタップすればインストールできる筈です。

実行して確認するとビルドできたみたいです。

Screenshot_20231011-071339.jpg

ewwも動きます

Screenshot_20231011-071704.jpg

次記事ではEmacsとTermuxを共存させるために行った環境設定について説明します。

おしまい

2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1