11
2

More than 1 year has passed since last update.

ElixirDesktopのiOSアプリ実装に苦労した話

Last updated at Posted at 2022-07-28

はじめに

Elixir Desktop は Phoenix LiveView を使って、 macOS でも Windows でも iOS でも Android でもネイティブアプリっぽいものが実装できるパッケージです

今回は iOS 版のサンプル実装を私のローカルマシン(macOS)上でビルドしようとして、
色々なエラーに引っ掛かった話を書いていきます

ビルド環境

  • macOS Monterey 12.4
  • XCode 13.4.1
  • asdf 0.10.0

私は色々な案件で色々な言語、ツールをワサワサ使う関係上、
開発言語やツールの実行バージョン管理に asdf を使っています

例えば Elixir なら asdf install elixir 1.13.4-otp-24 というようにするだけで
Elixir の指定バージョンがインストールでき、
asdf local elixir 1.13.4-otp-24 とするだけで、実行ディレクトリ配下では
Elixir の指定バージョンが使われるようになります
同じことが凡そ全ての開発言語でできるので非常に便利です

custom OTP のインストール

実装例のビルド手順は以下のようになっています

  1. Install the custom OTP version on your machine (*see known todos)

  2. Install xcode from the app store.

  3. Install brew, elixir, git, carthage, npm

    brew install elixir carthage git npm
    
  4. fork / git clone this project to your local disk:

    git clone https://github.com/elixir-desktop/ios-example-app.git
    
  5. Build the dependencies:

    cd ios-example-app && carthage update --use-xcframeworks
    
  6. Open the ios-example-app project with xcode

  7. Start the App

問題になるのは 1. で指定する custom OTP です

known todos には以下の記載があります

To have the embedded Erlang match the one you use for compilation you can install the same > version as the embedded:

mkdir -p ~/projects/
kerl build git https://github.com/diodechain/otp.git diode/beta 24.beta
kerl install 24.beta ~/projects/24.beta

kerl を使って、指定した git リポジトリの diode/beta ブランチから OTP をインストールするわけですね

実際、この OTP ではない通常の OTP24 や OTP25 を使ってビルドすると、実行時にエラーが発生します

が、私は kerl ではなく asdf を使っているため、上記の方法は使えません(使いたくありません)

というわけで、 asdf で Erlang を git リポジトリからインストールします

こんな感じにリポジトリの URL を環境変数で指定し、ブランチ名を asdf install の最後の引数に指定すれば良いようです

export OTP_GITHUB_URL="https://github.com/diodechain/otp"
asdf install erlang ref:diode/beta

これで OK かと思いきや、、、

この問題が発生してしまいました

macOS の Homebrew で入れていた wxWidgets が 3.2.0 だったことが原因です

$ brew info wxwidgets
wxwidgets: stable 3.2.0 (bottled), HEAD
Cross-platform C++ GUI toolkit
https://www.wxwidgets.org
/usr/local/Cellar/wxwidgets/3.2.0 (834 files, 25.4MB) *

3.1.6 の時点で erlang のビルドに影響のある変更が入っており、
今回入れようとしている OTP ではそれへの対応が入っていないのです

では、 3.1.5 を指定して入れようとすると、、、

$ brew install wxwidgets@3.1.5
Warning: No available formula with the name "wxwidgets@3.1.5". Did you mean wxwidgets@3.0?

となって、インストールできません

Homebrew のリポジトリー上から、 3.1.5 の情報が消えてしまっているのです

というわけで、 Homebrew のリポジトリーを過去に戻します

まず、 3.1.5 が存在していたコミットを探します

cd /usr/local/Homebrew/Library/Taps/homebrew/homebrew-core/Formula
git log --oneline wxwidgets.rb

しばらくすると、過去を遡ってちょっとずつ wxWidgets の履歴が出てきます

3.1.5 が出てきた時点で command + C で終了します

0141b0784c2 wxwidgets: update 3.2.0 bottle.
4b220a83c8d wxwidgets 3.2.0
725e88dc19c wxwidgets: update 3.1.7 bottle.
f02810ab10a wxwidgets 3.1.7
3d8559f4ec1 wxwidgets: update 3.1.6 bottle.
787b6c5eed0 wxwidgets 3.1.6
fc9831dc24b wxwidgets: update 3.1.5 bottle.
b3fddce87df wxwidgets: update 3.1.5 bottle.

3.1.5 のコミット番号 (fc9831dc24b) をコピーします

当該コミットをチェックアウトしてきます

git checkout fc9831dc24b wxwidgets.rb

この状態で、自動更新を OFF にして wxWidgets をインストールします
HOMEBREW_NO_AUTO_UPDATE=1 を付けていないと、リポジトリが更新されて結局最新版が入ってしまう

HOMEBREW_NO_AUTO_UPDATE=1 brew install wxwidgets

これで過去のバージョンが入りました

wxWidgets の 3.1.5 が入っている状態で、もう一度 Erlang をインストールします

asdf install erlang ref:diode/beta

実行すると、エラー終了はしませんが、途中で以下のようなメッセージが表示されます

sed: 1: "/^.*git,asdf_diode/beta$/d": undefined label 'eta$/d'

確認のため、インストールされているバージョンの一覧を表示してみます

$ asdf list erlang
  22.3.4.24
  24.3.3
  25.0.3

はい、入っていません

asdf のバグで、ブランチ名に / が入っていると、正常にインストールできないようです

これを修正するのも骨が折れるので、
リポジトリをフォークしてから新しくブランチ (beta) を作ります

これで、自分のリポジトリから OTP をインストールします

export OTP_GITHUB_URL="https://github.com/RyoWakabayashi/otp"
asdf install erlang ref:beta

改めてインストールされているバージョンの一覧を表示してみます

$ asdf list erlang
  22.3.4.24
  24.3.3
  25.0.3
  ref:beta

というわけで、ようやくインストールできました、、、

バージョン指定

xcode 等をインストールしてから、サンプル実装のリポジトリをクローンします

git clone https://github.com/elixir-desktop/ios-example-app.git
cd ios-example-app

asdf で管理している場合、以下のように Elixir と Erlang 、 Node.js のバージョンを指定します
※インストールしていない場合は先にインストールしておきます

asdf local elixir 1.13.4-otp-24
asdf local erlang ref:beta
asdf local nodejs 12.16.1

依存パッケージのインストール

Cathage で Swift の依存パッケージ (ZIPFoundation) をインストールします

carthage update --use-xcframeworks

XCode でプロジェクトを開く

XCode でプロジェクトを開きます

open todoapp.xcodeproj

そうすると、以下のように表示されます

スクリーンショット 2022-07-27 11.53.30.png

app.zip が赤いのは、ファイルが存在しないためです

Build Phase の Run Scripts を見てみましょう

スクリーンショット 2022-07-27 11.57.36.png

こんなスクリプトになっています

#!/bin/bash
set -e
# Setting up the PATH environment 
eval $(/opt/homebrew/bin/brew shellenv)
# Ensure compilation is using the correct runtime
. ~/projects/24.beta/activate
export PATH="$HOME/elixir/bin:$PATH"
export NVM_DIR="$HOME/.nvm"
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"  # This loads nvm

BASE=`pwd`
export MIX_ENV=prod
export MIX_TARGET=ios

if [ ! -d "elixir-app" ]; then
  git clone https://github.com/elixir-desktop/desktop-example-app.git elixir-app
  cd elixir-app
  mix deps.get
else
  cd elixir-app
fi

if [ ! -d "assets/node_modules" ]; then
  cd assets && npm i && cd ..
fi

if [ -f "$BASE/todoapp/app.zip" ]; then
  rm "$BASE/todoapp/app.zip"
fi

mix assets.deploy && \
  mix release --overwrite && \
  cd _build/ios_prod/rel/todo_app && \
  zip -9r "$BASE/todoapp/app.zip" lib/ releases/ --exclude "*.so"

ビルドの流れは以下のようになっています

  • 使用する言語のバージョンを指定
  • Desktop 版の実装コードをクローン
  • Elixir の依存パッケージを取得
  • JS の依存パッケージを取得
  • 古い app.zip (前回実行結果)を削除
  • Elixir のコードをリリースようにコンパイルして app.zip に圧縮する

app.zip が存在しなかったのは、ビルド時に作成されるものだったからですね

ビルドの実行とエラー対応

Team と Bundle Identifier を変更します

エミュレータでは動作しないので、 iPhone を接続して Run します

するとエラーが表示されます

ld: '/Users/oec/Library/Developer/Xcode/DerivedData/todoapp-fpynodswplkqbmfjqesdzwnvefvb/Build/Products/Debug-iphoneos/liberlang.a(erl_init.o)' does not contain bitcode. You must rebuild it with bitcode enabled (Xcode setting ENABLE_BITCODE), obtain an updated library from the vendor, or disable bitcode for this target. for architecture arm64
clang: error: linker command failed with exit code 1 (use -v to see invocation)

Build Settings -> Build Options -> Enable Bitcode を No に変更します

スクリーンショット 2022-07-27 12.13.46.png

Run Scripts の中で実行する場合、環境変数を引き継がない関係で、
どうしても正常にビルドできなかったので、プロジェクトのルート直下に以下の内容で run_mix.sh を作ります

#!/bin/zsh
set -e

BASE=`pwd`
export MIX_ENV=prod
export MIX_TARGET=ios

if [ ! -d "elixir-app" ]; then
  git clone https://github.com/elixir-desktop/desktop-example-app.git elixir-app
fi

cd elixir-app

mix clean
mix deps.clean --all
rm -f mix.lock
mix deps.get

if [ ! -d "assets/node_modules" ]; then
  cd assets && npm i && cd ..
fi

if [ -f "$BASE/todoapp/app.zip" ]; then
  rm "$BASE/todoapp/app.zip"
fi

mix assets.deploy && \
  mix release --overwrite && \
  cd _build/ios_prod/rel/todo_app && \
  zip -9r "$BASE/todoapp/app.zip" lib/ releases/ --exclude "*.so"

mix.lock を消しているのは、最新の Bridge を取得するためです

run_mix.sh にパーミッションを追加してから実行します

chmod +x run_mix.sh
./run_mix.sh

そうすると、 todoapp/app.zip が作成されます

Build Phase の Run Scripts を削除してから、
再び Run します

すると、以下のようなエラーが表示されます

'/usr/lib/swift/ZIPFoundation.framework/ZIPFoundation' (no such file),

フレームワークが正しくコピーできていないようです

General -> Frameworks, ... から、各フレームワークの Embed の値を Embed & Sign に変更します

スクリーンショット 2022-07-27 13.42.47.png

また実行すると、次のエラーが表示されます

[0m[22m[notice] Application todo_app exited: exited in: TodoApp.start(:normal, [])
    ** (EXIT) an exception was raised:
        ** (ErlangError) Erlang error: :enoent
            (elixir 1.13.4) lib/system.ex:1044: System.cmd("defaults", ["read", "-g", "AppleLocale"], [])
            (desktop 1.4.0) lib/desktop.ex:79: Desktop.language_code/0
            (desktop 1.4.0) lib/desktop.ex:51: Desktop.identify_default_locale/1
            (todo_app 1.0.0) lib/todo_app.ex:23: TodoApp.start/2
            (kernel 8.0) application_master.erl:293: :application_master.start_it_old/4

Elixir Desktop の lib/desktop.ex:79 を見てみます

    with MacOS <- Desktop.OS.type(),
         {locale, 0} <- System.cmd("defaults", ~w(read -g AppleLocale)),
         [code] <- Regex.run(~r/[a-z]+_[A-Z]+/, locale) do

どうやら、 MacOS ではないのに MacOS と判定されてしまっているようです

Desktop.OS.type() の定義を見てみます

  @spec type :: Linux | MacOS | Windows | Android | IOS
  def type() do
    case System.get_env("ELIXIR_DESKTOP_OS", nil) do
      "android" ->
        Android

      "ios" ->
        IOS

      _ ->
        case :os.type() do
          {:unix, :darwin} -> MacOS
          {:unix, :linux} -> Linux
          {:win32, _} -> Windows
        end
    end
  end

環境変数 ELIXIR_DESKTOP_OS が ios なら IOS になるようです

Edit Scheme を開き

スクリーンショット 2022-07-27 13.52.24.png

Run スキームの Arguments -> Environment Variables で
Name=ELIXIR_DESKTOP_OS Value=ios を追加します

スクリーンショット 2022-07-27 13.51.56.png

続いてのエラーはこちらです

[0m[22m[notice] Application todo_app exited: exited in: TodoApp.start(:normal, [])
    ** (EXIT) an exception was raised:
        ** (File.Error) could not make directory (with -p) "/private/var/mobile/Containers/Data/Application/3A3280A6-4B45-4C5E-A6BE-B7D20B4EF3AF/.config/todo": no such file or directory
            (elixir 1.13.4) lib/file.ex:316: File.mkdir_p!/1
            (todo_app 1.0.0) lib/todo_app.ex:24: TodoApp.start/2
            (kernel 8.0) application_master.erl:293: :application_master.start_it_old/4

ディレクトリーの作成に失敗しています

lib/todo_app.ex の24行目を見てみます


  def config_dir() do
    Path.join([Desktop.OS.home(), ".config", "todo"])
  end

  @app Mix.Project.config()[:app]

  def start(:normal, []) do
    Desktop.identify_default_locale(TodoWeb.Gettext)
    File.mkdir_p!(config_dir())

ホームディレクトリーの中の .config/todo ディレクトリーを作ろうとしています

しかし、どうやらホームディレクトリーの指定が誤っているようです

ios-example-app の todoapp/Bridge.swift を見ると、

        home = FileManager.default.urls(for: .libraryDirectory, in: .userDomainMask)[0].appendingPathComponent("io.elixirdesktop.example")

パスの指定が io.elixirdesktop.example 固定になってしまっています

これはいけません

ちゃんと設定から取ってくるようにします

        home = FileManager.default.urls(for: .libraryDirectory, in: .userDomainMask)[0].appendingPathComponent(Bundle.main.bundleIdentifier!)

これで再実行すると、、、

IMG_7F61B6421742-1.jpeg

やっと動きました!!

最後の Bundle Identifier のところは明確にハードコードで悪いので、 PR を出しておきました

おわりに

これで Elixir でモバイルアプリを作る準備ができました

11
2
3

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
11
2