- 投稿日:2020-12-04T22:02:06+09:00
Big Surアップデート後に特定のコマンドがkillされる
本当にしょうもない記事なのですが自分は1時間かかってしまっているので記録します。
同じ問題に直面した人の助けになれば幸いです。環境
iMac 2017
Mac OS 11.01
素人zsh環境起こった問題
OSX HighSierraからのアップデート後、zshの補完が上手くいかないことに疑問を覚えました。
brewのアップデートをしていないなと思いupgrade, updateどちらを試すもエラーが出ました。
pipコマンドやbrew、dockerなどの特定のコマンドが瞬時に強制終了されてしまう・・・
(python、vim、自作コマンドなんかは動く)
brewが悪そうだとアタリをつけてGit must be ...で検索をかけると以下の解決方法が載っていました。$ sudo rm -rf /Library/Developer/CommandLineTools $ xcode-select --installコマンドラインツールのバージョンが古い説、とのことです。
指示に従った後、一旦ターミナルを再起動させるも解決せず...
常に実行しているプロセスがあるためためらっていた本体の再起動をしました。・・・
−>解決しました解決法
再起動しないといけないです。なんでもそうですよね。
正直申しますとなぜ解決したのかがわかっていません。Xcodeのバージョンなのか、それとも他のところで詰まっていて再起動が解決したのか、両方なのか...
後学のためにご教授くださると大変嬉しく思います。
- 投稿日:2020-12-04T15:39:58+09:00
Elixir から Swift 5.3のコードを呼び出す方法(Autotoolsを使って / Apple Silicon M1チップにも対応)
この記事はElixir Advent Calendar 2020の10日目です。
昨日は @kobae964 さんの「Rustler で行儀の良い NIF を書く」でした。
さて,Apple Silicon M1チップの性能が明らかになるにつれて,今後,ElixirエコシステムからMacの潜在能力をフル活用することが求められてくるように思います(というか,是非活用したい)。そこで,この記事はElixirからSwift 5.3のコードを呼び出す方法について紹介しています。
Autotoolsを使ってObjective-CやSwiftのヘッダファイルや関数の存在判定などもできるようにする方法も示していますので,Elixir開発者だけでなく,iOSネイティブアプリやMacアプリの開発者にも部分的には有用かと思います。しかも,Apple Silicon M1チップ搭載のMacでも動作検証をしています!
この記事のGitHubレポジトリは https://github.com/zacky1972/swift_elixir_test です。下記に同様のもののつくりかたを詳説しています。
注意点: この記事はApple Silicon M1チップ搭載のMacに概ね対応していますが,(1)ErlangをARMネイティブでビルドして(2)かつRosetta 2モードでターミナルを起動した場合には,NIFのロード時にアーキテクチャの不一致によるエラーが発生し,NIFを実行できないという問題があります。この問題は,従来のNIFプログラム全般で発生する可能性のある問題だと認識しています。現在,さらに調査を進めて問題の解決に当たっているところです。
まずは
mix new
まずは
mix new
でプロジェクトを作ります。プロジェクト名はswift_elixir_test
としました。% mix new swift_elixir_test * creating README.md * creating .formatter.exs * creating .gitignore * creating mix.exs * creating lib * creating lib/swift_elixir_test.ex * creating test * creating test/test_helper.exs * creating test/swift_elixir_test_test.exs Your Mix project was created successfully. You can use "mix" to compile it, test it, and more: cd swift_elixir_test mix test Run "mix help" for more commands. %書かれている指示に従って,進めます。
% cd swift_elixir_test swift_elixir_test % mix test Compiling 1 file (.ex) Generated swift_elixir_test app .. Finished in 0.03 seconds 1 doctest, 1 test, 0 failures Randomized with seed 231147 swift_elixir_test %ここで自動生成されたコードを修正しておきます。
lib/swift_elixir_test.exdefmodule SwiftElixirTest do @moduledoc """ Documentation for `SwiftElixirTest`. """ @doc """ Hello world. ## Examples iex> SwiftElixirTest.hello() :world """ def hello do :world end endこれを次のようにします。(
hello
関数を削除)lib/swift_elixir_test.exdefmodule SwiftElixirTest do @moduledoc """ Documentation for `SwiftElixirTest`. """ endそして自動生成されたテストコードも修正します。
test/swift_elixir_test_test.exsdefmodule SwiftElixirTestTest do use ExUnit.Case doctest SwiftElixirTest test "greets the world" do assert SwiftElixirTest.hello() == :world end endこれを次のようにします。
test/swift_elixir_test_test.exsdefmodule SwiftElixirTestTest do use ExUnit.Case doctest SwiftElixirTest endここで,
mix test
を実行して,テストが0個になることを確認します。swift_elixir_test % mix test Compiling 1 file (.ex) Finished in 0.02 seconds 0 failures Randomized with seed 71756 swift_elixir_test %ここまでできたら,
git
に登録しましょう。swift_elixir_test % git init swift_elixir_test % git add -A swift_elixir_test % git commit -m "initial commit" swift_elixir_test % git branch -M mainAutoconf の初期設定
ここではAutoconfを使ってビルド時の環境を認識するようにします。ただしAutoconfで生成した環境認識スクリプト
configure
は並列ビルドできないという欠点があるため遅いという難点があります。せっかく並列実行に強いElixirなので,将来はElixirで並列実行できるようにしたいですが,将来課題とします。まず空の
configure.ac
を作成します。configure.acdnl Process this file with autoconf to produce a configure script AC_INIT()
dnl
で始まる行はコメント行です。AC_INIT()
はautoconf
に初期化を指示します。パラメータを与えるのが普通なのですが,いったん無しで実行します。この状態で
autoconf
を実行します。もしHomebrewを使っているならあらかじめ次のコマンドを実行しておきます。swift_elixir_test % brew install autoconf
では
autoconf
を実行しましょう。swift_elixir_test % autoconfそうすると次のファイルが生成されます。
autom4te.cache configure
.gitignore
に下記を追記してgit
が追加ファイルを無視するようにしましょう。.gitignore# For Autoconf /autom4te.cache/ # For configure /configure
configure
を実行してみます。swift_elixir_test % ./configureすると
config.log
が生成されるので,これも.gitignore
に下記を追記して無視するように設定します。.gitignore# For configure /config.log /configure
elixir_make
でconfigure
を呼ぶ
elixir_make
を使うとmix compile
をしたときにmake
を用いたビルドをしてくれます。Elixirの作者のJosé Valim(ジョゼ・ヴァリム)にelixir_make
を使ってconfigure
を呼び出す方法を教えてもらいました( https://github.com/elixir-lang/elixir_make/issues/42 )ので,紹介したいと思います。まず,
mix.exs
を書き換えてelixir_make
をインストールします。mix.exs
の下記の部分がインストールするライブラリを指定する部分です。mix.exsdefp deps do [ # {:dep_from_hexpm, "~> 0.3.0"}, # {:dep_from_git, git: "https://github.com/elixir-lang/my_dep.git", tag: "0.1.0"} ] endこれを次のように書き換えます。
mix.exsdefp deps do [ {:elixir_make, "~> 0.6.2", runtime: false} ] endそれから次のコマンドを実行します。
swift_elixir_test % mix deps.getこれで
elixir_make
がインストールされました。次に
mix.exs
に次のような関数を追加します。System.cmd("#{File.cwd!()}/configure", []) で
./configure` を実行することになります。mix.exsdefp configure(_args) do System.cmd("#{File.cwd!()}/configure", []) endそして
mix.exs
の下記の部分がプロジェクト情報なのですが,これを書き換えます。mix.exsdef project do [ app: :swift_elixir_test, version: "0.1.0", elixir: "~> 1.11", start_permanent: Mix.env() == :prod, deps: deps() ] end次のようにします。
mix.exsdef project do [ app: :swift_elixir_test, version: "0.1.0", elixir: "~> 1.11", start_permanent: Mix.env() == :prod, deps: deps(), compilers: [:elixir_make] ++ Mix.compilers, aliases: [compile: [&configure/1]] ] endこのようにすると,
make
を呼び出す代わりに./configure
を呼び出します。mix compile
を実行してエラーがないことを確認しましょう。(なお,この時点ではmake
を呼んでいません)Automakeでライブラリを生成
次にAutomakeの設定をします。
ElixirからSwiftを呼ぶために,ElixirからCで生成したネイティブコードをリンクして呼出すNIFを利用します。NIFで呼出すためには動的ライブラリとして生成しますので,Automakeで動的ライブラリを生成するように設定する必要があります。
Cのソースコードを
native/libnif.c
に配置しましょう。次のコマンドを実行します。swift_elixir_test % mkdir -p native
native/libnif.c
を作成します。native/libnif.c#include <erl_nif.h>
erl_nif.h
というのはNIF APIのヘッダファイルです。
Makefile.am
を次のように作成します。Makefile.amAUTOMAKE_OPTIONS = subdir-objects ACLOCAL_AMFLAGS = -I m4 lib_LTLIBRARIES = priv/libnif.la priv_libnif_la_SOURCES = native/libnif.c priv_libnif_la_CFLAGS = $(CFLAGS) $(ERL_CFLAGS) priv_libnif_la_LDFLAGS = $(LDFLAGS) $(ERL_LDFLAGS) -shared -module -avoid-version -export-dynamic説明は次のとおりです。
AUTOMAKE_OPTIONS = subdir-objects
でサブディレクトリにソースコード等を配置することを指定します。ACLOCAL_AMFLAGS = -I m4
はaclocal
で設定した値を読み込みます。lib_LTLIBRARIES = priv/libnif.la
はビルドしたいライブラリを指定します。拡張子が.la
ですが,Automakeでは一律にこのように指定するので,心配しないでください。priv_libnif_la_
というのはpriv/libnif.la
に対応するオプションであることを示す接頭辞です。
priv_libnif_la_SOURCES
でソースコードを指定します。ここではnative/libnif.c
をコンパイルします。priv_libnif_la_CFLAGS
でコンパイルする時のCFLAGS
の値を決めます。ここでは,CFLAGS
とERL_CFLAGS
の値を設定します。ERL_CFLAGS
は後でconfigure.ac
の中で設定しますが,Erlang が提供するヘッダファイルの情報などを定義します。priv_libnif_la_LDFLAGS
で同様にリンクする時のLDFLAGS
の値を決めます。ここでは,LDFLAGS
とERL_LDFLAGS
の値を設定します。ERL_LDFLAGS
は,ERL_CFLAGS
と同様です。動的な共有ライブラリを生成するために,-shared
-module
-export-dynamic
を指定します。.so
というようにバージョン番号を記載しないようにするために-avoid-version
を指定します。そして
configure.ac
を次のように変更します。configure.acdnl Process this file with autoconf to produce a configure script AC_INIT([priv/.libs/libnif.so], [1.0]) AC_CONFIG_MACRO_DIRS([m4]) AM_INIT_AUTOMAKE([-Wall -Werror foreign]) AC_ARG_VAR([ELIXIR], [Elixir]) AC_ARG_VAR([ERL_EI_INCLUDE_DIR], [ERL_EI_INCLUDE_DIR]) AC_ARG_VAR([ERL_EI_LIBDIR], [ERL_EI_LIBDIR]) AC_ARG_VAR([CROSSCOMPILE], [CROSSCOMPILE]) AC_ARG_VAR([ERL_CFLAGS], [ERL_CFLAGS]) AC_ARG_VAR([ERL_LDFLAGS], [ERL_LDFLAGS]) AC_PROG_CC AM_PROG_AR AC_PATH_PROG(ELIXIR, $ELIXIR, elixir) AC_MSG_CHECKING([setting ERL_EI_INCLUDE_DIR]) if test "x$ERL_EI_INCLUDE_DIR" = "x"; then AC_SUBST([ERL_EI_INCLUDE_DIR], [$(LC_ALL=en_US.UTF-8 $ELIXIR --eval ':code.root_dir |> to_string() |> Kernel.<>("/usr/include") |> IO.puts')]) fi AC_MSG_RESULT([$ERL_EI_INCLUDE_DIR]) AC_MSG_CHECKING([setting ERL_EI_LIBDIR]) if test "x$ERL_EI_LIBDIR" = "x"; then AC_SUBST([ERL_EI_LIBDIR], [$(LC_ALL=en_US.UTF-8 $ELIXIR --eval ':code.root_dir |> to_string() |> Kernel.<>("/usr/lib") |> IO.puts')]) fi AC_MSG_RESULT([$ERL_EI_LIBDIR]) AC_MSG_CHECKING([setting ERL_CFLAGS]) if test "x$ERL_CFLAGS" = "x"; then AC_SUBST([ERL_CFLAGS], [$(LC_ALL=en_US.UTF-8 $ELIXIR --eval '"-I#{System.get_env("ERL_EI_INCLUDE_DIR", "#{to_string(:code.root_dir)}/usr/include")}" |> IO.puts')]) fi AC_MSG_RESULT([$ERL_CFLAGS]) AC_MSG_CHECKING([setting ERL_LDFLAGS]) if test "x$ERL_LDFLAGS" = "x"; then AC_SUBST([ERL_LDFLAGS], [$(LC_ALL=en_US.UTF-8 $ELIXIR --eval '"-L#{System.get_env("ERL_EI_LIBDIR", "#{to_string(:code.root_dir)}/usr/lib")}" |> IO.puts')]) fi AC_MSG_RESULT([$ERL_LDFLAGS]) LT_INIT() AC_CONFIG_FILES([Makefile]) AC_OUTPUT説明は次のとおりです。
AC_INIT
に生成するライブラリの情報を与えます。AC_CONFIG_MACRO_DIRS([m4])
でaclocal
で得られた設定を読むようにします。AC_INIT_AUTOMAKE
で Automake の使用を宣言します。オプションでエラーや警告を表示するようにしています。AC_ARG_VAR
で,configure
に与える環境変数を定義します。第1引数に変数名,第2引数にconfigure --help
の時に表示する説明を記載します。本当は第2引数をていねいにドキュメンテーションすべきところですが,手を抜いています。AC_PROG_CC
とAC_PROG_AR
はそれぞれ,CC
とAR
で指定されたコンパイラとリンカが存在することを確認します。AC_PATH_PROG(ELIXIR, $ELIXIR, elixir)
で環境変数ELIXIR
が設定されている場合にはそのパス上のプログラムが,設定されていない時にはelixir
が,PATH
上に存在するかを確認してその結果を表示します。- その後の
AC_MSG_CHECKING
からAC_MSG_RESULT
の一塊は,それぞれErlangに関連する環境変数が設定されているかを確認します。
AC_MSG_CHECKING([setting ERL...])
で確認中のメッセージを表示します。if test "x$ERL..." = "x"; then ... fi
で環境変数ERL...
が設定されているかを確認します。このような書き方は,シェルで移植性の高い記述をするためのAutoconfでは定番の書き方です。AC_SUBST
は第1引数の環境変数に第2引数の値を代入します。- ここでは
elixir --eval ワンライナープログラム
とすることで,それぞれ少しずつ異なるElixirのワンライナーのプログラムを実行して設定に必要なパスを取得しています。LC_ALL=en_US.UTF-8
を設定しているのはLinux環境でロケールに関する警告を抑制するためです。AC_MSG_RESULT
で設定された結果を表示します。- Elixirのワンライナーのプログラムは次のようになっています。
:code.root_dir |> to_string()
とすることで実行する Erlang の処理系の存在するパスを表示します。この値を仮に$1
としましょう。ERL_EI_INCLUDE_DIR
:$1/usr/include
を設定します。ERL_EI_LIBDIR
:$1/usr/lib
を設定します。ERL_CFLAGS
:ERL_EI_INCLUDE_DIR
が設定されているならば-I$ERL_EI_INCLUDE_DIR
を,そうでなければ-I$1/usr/include
を設定します。ERL_LDFLAGS
:ERL_EI_LIBDIR
が設定されているならば-L$ERL_EI_LIBDIR
を,そうでなければ-L$1/usr/lib
を設定します。LT_INIT
でLibtoolの初期化をします。AC_CONFIG_FILES([Makefile])
でMakefile
を出力するように設定します。AC_OUTPUT
で,以上の結果を出力します。これらのファイルを記述した後,もしHomebrewを使っているならあらかじめ次のコマンドを実行しておきます。
swift_elixir_test % brew install automake libtool
そして次のコマンドを実行します。
swift_elixir_test % autoreconf -i
.gitignore
に次を追記しましょう。.gitignore# For Autoconf /autom4te.cache/ /Makefile.in /aclocal.m4 /libtool /ar-lib /compile /install-sh /ltmain.sh /m4/ /missing /depcomp # For configure /config.log /config.status /config.guess /config.sub /configure # For build files /native/.deps /native/.dirstamp /native/.libs /native/*.o /native/*.lo /priv Makefile
mix.exs
のproject
情報を次のように変えます。mix.exsdef project do [ app: :swift_elixir_test, version: "0.1.0", elixir: "~> 1.11", start_permanent: Mix.env() == :prod, deps: deps(), compilers: [:elixir_make] ++ Mix.compilers(), aliases: [ compile: [&autoreconf/1, &configure/1, "compile"], clean: [&autoreconf/1, &configure/1, "clean"] ], make_clean: ["clean"] ] endまた,
autoreconf
を呼び出すようにmix.exs
に次の関数を足します。mix.exsdefp autoreconf(_args) do System.cmd("autoreconf", ["-i"]) endこれで
mix compile
を実行します。エラーなくビルドが終わりましたか? 出来たら次のようにして動的ライブラリが出来上がっていることを確認します。swift_elixir_test % file priv/.libs/libnif.so priv/.libs/libnif.so: Mach-O 64-bit bundle x86_64やった!
elixir_make
でNIFのビルドうまくいったので,
native/libnif.c
を仮実装します。native/libnif.c#include <stdlib.h> #include <erl_nif.h> static ERL_NIF_TERM test(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) { ERL_NIF_TERM atom_error = enif_make_atom(env, "error"); return enif_make_tuple(env, 2, atom_error, enif_make_atom(env, "not_implemented")); } static ErlNifFunc nif_funcs[] = { {"test", 0, test} }; ERL_NIF_INIT(Elixir.SwiftElixirTest, nif_funcs, NULL, NULL, NULL, NULL)この段階で
mix compile
してエラーがなくビルドできることを確認します。説明は次のとおりです。
NULL
を使うために,#include <stdlib.h>
としました。#include <erl_nif.h>
はErlang の NIF API を定義しているヘッダファイルをインクルードします。もしここでエラーになるようならば,Automakeの設定のところが間違えていますので,見直してください。test
関数の定義はNIF APIに沿っています。第1引数が実行時環境,第2引数と第3引数で可変長の引数を形成しています。ERL_NIF_TERM
型はElixir/Erlangの変数のインタフェースです。enif_make_atom
でアトムを生成します。第1引数が実行時環境,第2引数がアトムの名前です。enif_make_tuple
でタプルを生成します。第1引数が実行時環境,第2引数から可変長の引数を形成していて,第2引数が要素数,第3引数以下が各要素です。- 仮に
{:error, :not_implemented"}
を返しています。nif_funcs
で関数を登録します。この場合の意味としてはElixirのtest関数の引数の数(アリティ)が0であるようなtest
という名称の関数を定義しています。ERL_NIF_INIT
でモジュールを登録します。第1引数がモジュール名,第2引数がnif_funcs
,第3〜6引数は初期化やリロード時の設定をする関数を登録します。ここでは仮に第3〜6引数にはNULL
を登録します。次に
lib/swift_elixir_test.ex
を変更します。lib/swift_elixir_test.exdefmodule SwiftElixirTest do require Logger @moduledoc """ Documentation for `SwiftElixirTest`. """ @on_load :load_nif def load_nif do nif_file = '#{:code.priv_dir(:swift_elixir_test)}/.libs/libnif' case :erlang.load_nif(nif_file, 0) do :ok -> :ok {:error, {:reload, _}} -> :ok {:error, reason} -> Logger.warn("Failed to load NIF: #{inspect(reason)}") end end def test(), do: raise("NIF test/0 not implemented") end説明は次のとおりです。
require Logger
とすることで,デバッグ等のログ出力を行うモジュールを呼び出せるようにします。@on_load :load_nif
とすることで,このモジュールを読み込む時,load_nif
関数を呼び出します。nif_file
に読み込むNIFライブラリの情報を与えます。
nif_file='...'
のようにシングルクォーテーションであるのに注意してください。Erlangに直接渡す文字列なので,char listにしてあります。:code_priv_dir
は,第1引数で指定したモジュールのpriv
ディレクトリを参照する関数です。SwiftElixirTest
モジュールのpriv/.libs/libnif.so
を読み込むので,.so
を取って:code_priv_dir(:swift_elixir_test)/.libs/libnif
とします。:erlang.load_nif
はNIFをロードする関数です。:ok
もしくは{:error, {:reload, ...}}
が返ってきた時には正常終了します。- それ以外の
{:error, ...}
が返ってきた時には,...
をreason
に代入して,Logger
を使って警告表示をします。test
関数の定義がNIF関数へのスタブです。NIFを定義する場合の定番で,呼び出した時に例外を発生するようにしています。NIFライブラリが正常に読み込めると上書きされて,NIFを呼び出すようになります。ここまで出来たら
iex -S mix
を実行してみましょう。少し待った後に,次のように正常に起動しましたか?swift_elixir_test % iex -S mix Erlang/OTP 23 [erts-11.1.2] [source] [64-bit] [smp:6:6] [ds:6:6:10] [async-threads:1] [hipe] make: Nothing to be done for `all'. Interactive Elixir (1.11.2) - press Ctrl+C to exit (type h() ENTER for help) iex(1)>もし実際にビルドしている時には次のように表示されます。
swift_elixir_test % iex -S mix Erlang/OTP 23 [erts-11.1.2] [source] [64-bit] [smp:6:6] [ds:6:6:10] [async-threads:1] [hipe] /bin/sh ./libtool --tag=CC --mode=compile gcc -DPACKAGE_NAME=\"priv/.libs/libnif.so\" -DPACKAGE_TARNAME=\"priv--libs-libnif-so\" -DPACKAGE_VERSION=\"1.0\" -DPACKAGE_STRING=\"priv/.libs/libnif.so\ 1.0\" -DPACKAGE_BUGREPORT=\"\" -DPACKAGE_URL=\"\" -DPACKAGE=\"priv--libs-libnif-so\" -DVERSION=\"1.0\" -DSTDC_HEADERS=1 -DHAVE_SYS_TYPES_H=1 -DHAVE_SYS_STAT_H=1 -DHAVE_STDLIB_H=1 -DHAVE_STRING_H=1 -DHAVE_MEMORY_H=1 -DHAVE_STRINGS_H=1 -DHAVE_INTTYPES_H=1 -DHAVE_STDINT_H=1 -DHAVE_UNISTD_H=1 -DHAVE_DLFCN_H=1 -DLT_OBJDIR=\".libs/\" -I. -g -O2 -I/Users/zacky/.asdf/installs/erlang/23.1.2/usr/include -g -O2 -MT native/priv_libnif_la-libnif.lo -MD -MP -MF native/.deps/priv_libnif_la-libnif.Tpo -c -o native/priv_libnif_la-libnif.lo `test -f 'native/libnif.c' || echo './'`native/libnif.c libtool: compile: gcc -DPACKAGE_NAME=\"priv/.libs/libnif.so\" -DPACKAGE_TARNAME=\"priv--libs-libnif-so\" -DPACKAGE_VERSION=\"1.0\" "-DPACKAGE_STRING=\"priv/.libs/libnif.so 1.0\"" -DPACKAGE_BUGREPORT=\"\" -DPACKAGE_URL=\"\" -DPACKAGE=\"priv--libs-libnif-so\" -DVERSION=\"1.0\" -DSTDC_HEADERS=1 -DHAVE_SYS_TYPES_H=1 -DHAVE_SYS_STAT_H=1 -DHAVE_STDLIB_H=1 -DHAVE_STRING_H=1 -DHAVE_MEMORY_H=1 -DHAVE_STRINGS_H=1 -DHAVE_INTTYPES_H=1 -DHAVE_STDINT_H=1 -DHAVE_UNISTD_H=1 -DHAVE_DLFCN_H=1 -DLT_OBJDIR=\".libs/\" -I. -g -O2 -I/Users/zacky/.asdf/installs/erlang/23.1.2/usr/include -g -O2 -MT native/priv_libnif_la-libnif.lo -MD -MP -MF native/.deps/priv_libnif_la-libnif.Tpo -c native/libnif.c -fno-common -DPIC -o native/.libs/priv_libnif_la-libnif.o libtool: compile: gcc -DPACKAGE_NAME=\"priv/.libs/libnif.so\" -DPACKAGE_TARNAME=\"priv--libs-libnif-so\" -DPACKAGE_VERSION=\"1.0\" "-DPACKAGE_STRING=\"priv/.libs/libnif.so 1.0\"" -DPACKAGE_BUGREPORT=\"\" -DPACKAGE_URL=\"\" -DPACKAGE=\"priv--libs-libnif-so\" -DVERSION=\"1.0\" -DSTDC_HEADERS=1 -DHAVE_SYS_TYPES_H=1 -DHAVE_SYS_STAT_H=1 -DHAVE_STDLIB_H=1 -DHAVE_STRING_H=1 -DHAVE_MEMORY_H=1 -DHAVE_STRINGS_H=1 -DHAVE_INTTYPES_H=1 -DHAVE_STDINT_H=1 -DHAVE_UNISTD_H=1 -DHAVE_DLFCN_H=1 -DLT_OBJDIR=\".libs/\" -I. -g -O2 -I/Users/zacky/.asdf/installs/erlang/23.1.2/usr/include -g -O2 -MT native/priv_libnif_la-libnif.lo -MD -MP -MF native/.deps/priv_libnif_la-libnif.Tpo -c native/libnif.c -o native/priv_libnif_la-libnif.o >/dev/null 2>&1 mv -f native/.deps/priv_libnif_la-libnif.Tpo native/.deps/priv_libnif_la-libnif.Plo /bin/sh ./libtool --tag=CC --mode=link gcc -g -O2 -I/Users/zacky/.asdf/installs/erlang/23.1.2/usr/include -g -O2 -L/Users/zacky/.asdf/installs/erlang/23.1.2/usr/lib -shared -module -avoid-version -export-dynamic -o priv/libnif.la -rpath /usr/local/lib native/priv_libnif_la-libnif.lo libtool: link: gcc -Wl,-undefined -Wl,dynamic_lookup -o priv/.libs/libnif.so -bundle native/.libs/priv_libnif_la-libnif.o -L/Users/zacky/.asdf/installs/erlang/23.1.2/usr/lib -g -O2 -g -O2 libtool: link: ( cd "priv/.libs" && rm -f "libnif.la" && ln -s "../libnif.la" "libnif.la" ) Compiling 1 file (.ex) Generated swift_elixir_test app Interactive Elixir (1.11.2) - press Ctrl+C to exit (type h() ENTER for help) iex(1)>では次のように
SwiftElixirTest.test
関数を呼び出して,仮の実装である{:error, :not_implemented}
が返ってくることを確かめましょう。iex(1)> SwiftElixirTest.test {:error, :not_implemented} iex(2)>Autoconf/AutomakeでMacかどうかを判別するには
まず
configure.ac
を次のように書き換えて,OSを認識するようにしましょう。さしあたり,macOSとLinuxで動くようにします。configure.acdnl Process this file with autoconf to produce a configure script AC_INIT([priv/.libs/libnif.so], [1.0]) AC_CANONICAL_BUILD AC_CANONICAL_HOST AC_CANONICAL_TARGET AC_CONFIG_MACRO_DIRS([m4]) AM_INIT_AUTOMAKE([-Wall -Werror foreign]) AC_ARG_VAR([ELIXIR], [Elixir]) AC_ARG_VAR([ERL_EI_INCLUDE_DIR], [ERL_EI_INCLUDE_DIR]) AC_ARG_VAR([ERL_EI_LIBDIR], [ERL_EI_LIBDIR]) AC_ARG_VAR([CROSSCOMPILE], [CROSSCOMPILE]) AC_ARG_VAR([ERL_CFLAGS], [ERL_CFLAGS]) AC_ARG_VAR([ERL_LDFLAGS], [ERL_LDFLAGS]) AC_ARG_VAR([OBJC_FLAGS], [OBJC_FLAGS]) AC_PROG_CC build_linux=no build_mac=no all_mac=no case "${host_os}" in linux*) build_linux=yes ;; cygwin*|mingw*) AC_MSG_ERROR([OS $host_os on Windows is not supported]) ;; darwin*) case "${build_os}" in darwin*) case "${target_os}" in darwin*) all_mac=yes AC_PATH_PROG(XCRUN, xcrun) ;; *) ;; esac ;; *) ;; esac build_mac=yes ;; *) AC_MSG_ERROR([OS $host_os is not suppurted]) ;; esac AM_CONDITIONAL([LINUX], [test "x$build_linux" = "xyes"]) AM_CONDITIONAL([OSX], [test "x$build_mac" = "xyes"]) AM_CONDITIONAL([ALLOSX], [test "x$all_mac" = "xyes"]) AM_PROG_AR AC_PATH_PROG(ELIXIR, $ELIXIR, elixir) AC_MSG_CHECKING([setting ERL_EI_INCLUDE_DIR]) if test "x$ERL_EI_INCLUDE_DIR" = "x"; then AC_SUBST([ERL_EI_INCLUDE_DIR], [$(LC_ALL=en_US.UTF-8 $ELIXIR --eval ':code.root_dir |> to_string() |> Kernel.<>("/usr/include") |> IO.puts')]) fi AC_MSG_RESULT([$ERL_EI_INCLUDE_DIR]) AC_MSG_CHECKING([setting ERL_EI_LIBDIR]) if test "x$ERL_EI_LIBDIR" = "x"; then AC_SUBST([ERL_EI_LIBDIR], [$(LC_ALL=en_US.UTF-8 $ELIXIR --eval ':code.root_dir |> to_string() |> Kernel.<>("/usr/lib") |> IO.puts')]) fi AC_MSG_RESULT([$ERL_EI_LIBDIR]) AC_MSG_CHECKING([setting ERL_CFLAGS]) if test "x$ERL_CFLAGS" = "x"; then AC_SUBST([ERL_CFLAGS], [$(LC_ALL=en_US.UTF-8 $ELIXIR --eval '"-I#{System.get_env("ERL_EI_INCLUDE_DIR", "#{to_string(:code.root_dir)}/usr/include")}" |> IO.puts')]) fi AC_MSG_RESULT([$ERL_CFLAGS]) AC_MSG_CHECKING([setting ERL_LDFLAGS]) if test "x$ERL_LDFLAGS" = "x"; then AC_SUBST([ERL_LDFLAGS], [$(LC_ALL=en_US.UTF-8 $ELIXIR --eval '"-L#{System.get_env("ERL_EI_LIBDIR", "#{to_string(:code.root_dir)}/usr/lib")}" |> IO.puts')]) fi AC_MSG_RESULT([$ERL_LDFLAGS]) LT_INIT() AC_CONFIG_FILES([Makefile]) AC_OUTPUT追加分を説明します。
AC_CANONICAL_BUILD
,AC_CANONICAL_HOST
,AC_CANONICAL_TARGET
を指定することで,それぞれ,ビルド時,ホスト,ターゲットのCPU,ベンダー,OSの情報を得ることが出来るようになります。これらは,AC_INIT
の直後に置くのが賢明です。OBJC_FLAGS
という変数を足しました。build_linux=no
からAM_CONDITIONAL([ALLOSX], [test "x$all_mac" = "xyes"])
までがOSの種類の判別です。さしあたり,私が準備できる検証環境であるLinuxの場合とmacOSの場合にのみビルドができるようにしています。macOSの場合は,さらにビルド時,ホスト,ターゲットがいずれもmacOSの場合でのみ,ALLOSX
という条件を成立させるようにしています。- また,この条件が成立した時にのみ,Xcodeのコマンドラインツールである
xcrun
がパス上に存在するかを確認しています。次に
Makefile.am
を次のように変更します。Makefile.amAUTOMAKE_OPTIONS = subdir-objects ACLOCAL_AMFLAGS = -I m4 lib_LTLIBRARIES = priv/libnif.la priv_libnif_la_SOURCES = native/libnif.c if ALLOSX priv_libnif_la_CFLAGS = -DALLOSX $(CFLAGS) $(ERL_CFLAGS) else priv_libnif_la_CFLAGS = $(CFLAGS) $(ERL_CFLAGS) endif priv_libnif_la_LDFLAGS = $(LDFLAGS) $(ERL_LDFLAGS) -shared -module -avoid-version -export-dynamic
ALLOSX
が成立している場合,すなわちビルド時,ホスト,ターゲットがいずれもmacOSの場合に,マクロALLOSX
を定義してnative/libnif.c
をコンパイルするようにしています。これで,
native/libnif.c
中で#ifdef ALLOSX
とすれば,ビルド時,ホスト,ターゲットがいずれもmacOSの場合か,それ以外かを判別してプログラムコードを書き分けることが可能になります。NIFからObjective-Cのコードを呼び出す
では,ビルド時,ホスト,ターゲットがいずれもmacOSの場合に,次のようなObjective-Cのコードを呼び出してみましょう。
caller.m#import <Foundation/Foundation.h> #import "caller.h" void caller() { NSLog(@"Hello world from Objective-C."); }caller.h#ifndef CALLER_H #define CALLER_H void caller(); #endif // CALLER_HObjective-Cのコードと言いつつ,ほぼCのコードですが,このcaller関数を起点に任意のObjective-Cのコードを呼び出せると思ってください。さしあたり,
Foundation
に定義されているNSLog
を用いて,Hello, worldしたいと思います。ビルド・リンクするには,
Makefile.am
を次のようにします。Makefile.amAUTOMAKE_OPTIONS = subdir-objects ACLOCAL_AMFLAGS = -I m4 lib_LTLIBRARIES = priv/libnif.la priv_libnif_la_SOURCES = native/libnif.c if ALLOSX priv_libnif_la_LIBADD = $(LIBOBJS) native/caller.lo native/caller.lo: native/caller.m native/caller.h $(LIBTOOL) --mode=compile xcrun clang -c $(OBJC_FLAGS) $(CFLAGS) -o $@ $< endif if ALLOSX priv_libnif_la_CFLAGS = -DALLOSX $(CFLAGS) $(ERL_CFLAGS) else priv_libnif_la_CFLAGS = $(CFLAGS) $(ERL_CFLAGS) endif priv_libnif_la_LDFLAGS = $(LDFLAGS) $(ERL_LDFLAGS) -shared -module -avoid-version -export-dynamic追加分の説明は次のとおりです。
priv_libnif_la_LIBADD
として,priv/.libs/libnif.so
にnative/caller.lo
を追加するようにしています。$(LIBOBJS)
は,それまでのオブジェクトファイル群を登録している変数です。native/caller.lo: native/caller.m native/caller.h
として依存関係を定義しています。$(LIBTOOL) --mode=compile xcrun clang -c $(OBJC_FLAGS) $(CFLAGS) -o $@ $<
とすることで,XcodeのClangを明示的に呼び出してコンパイルし,Libtoolを使って.lo
形式に変換しています。XcodeのClangを明示的に呼び出すことで,Framework
をリンクしてくれますし,Swiftコードをリンクした時にもバージョンの不一致を避けられます。Foundation と
NSLog
の動作確認(Objective-C)せっかくAutotoolsを使っているので,試しにFoundationと
NSLog
が動作するかをチェックするスクリプトを導入してみましょう。configure.ac
を次のようにします。configure.acdnl Process this file with autoconf to produce a configure script AC_INIT([priv/.libs/libnif.so], [1.0]) AC_CANONICAL_BUILD AC_CANONICAL_HOST AC_CANONICAL_TARGET AC_CONFIG_MACRO_DIRS([m4]) AM_INIT_AUTOMAKE([-Wall -Werror foreign]) AC_ARG_VAR([ELIXIR], [Elixir]) AC_ARG_VAR([ERL_EI_INCLUDE_DIR], [ERL_EI_INCLUDE_DIR]) AC_ARG_VAR([ERL_EI_LIBDIR], [ERL_EI_LIBDIR]) AC_ARG_VAR([CROSSCOMPILE], [CROSSCOMPILE]) AC_ARG_VAR([ERL_CFLAGS], [ERL_CFLAGS]) AC_ARG_VAR([ERL_LDFLAGS], [ERL_LDFLAGS]) AC_ARG_VAR([OBJC_FLAGS], [OBJC_FLAGS]) AC_PROG_CC build_linux=no build_mac=no all_mac=no case "${host_os}" in linux*) build_linux=yes ;; cygwin*|mingw*) AC_MSG_ERROR([OS $host_os on Windows is not supported]) ;; darwin*) case "${build_os}" in darwin*) case "${target_os}" in darwin*) all_mac=yes AC_PATH_PROG(XCRUN, xcrun) ;; *) ;; esac ;; *) ;; esac build_mac=yes ;; *) AC_MSG_ERROR([OS $host_os is not suppurted]) ;; esac AM_CONDITIONAL([LINUX], [test "x$build_linux" = "xyes"]) AM_CONDITIONAL([OSX], [test "x$build_mac" = "xyes"]) AM_CONDITIONAL([ALLOSX], [test "x$all_mac" = "xyes"]) AM_PROG_AR AC_PATH_PROG(ELIXIR, $ELIXIR, elixir) AC_MSG_CHECKING([setting ERL_EI_INCLUDE_DIR]) if test "x$ERL_EI_INCLUDE_DIR" = "x"; then AC_SUBST([ERL_EI_INCLUDE_DIR], [$(LC_ALL=en_US.UTF-8 $ELIXIR --eval ':code.root_dir |> to_string() |> Kernel.<>("/usr/include") |> IO.puts')]) fi AC_MSG_RESULT([$ERL_EI_INCLUDE_DIR]) AC_MSG_CHECKING([setting ERL_EI_LIBDIR]) if test "x$ERL_EI_LIBDIR" = "x"; then AC_SUBST([ERL_EI_LIBDIR], [$(LC_ALL=en_US.UTF-8 $ELIXIR --eval ':code.root_dir |> to_string() |> Kernel.<>("/usr/lib") |> IO.puts')]) fi AC_MSG_RESULT([$ERL_EI_LIBDIR]) AC_MSG_CHECKING([setting ERL_CFLAGS]) if test "x$ERL_CFLAGS" = "x"; then AC_SUBST([ERL_CFLAGS], [$(LC_ALL=en_US.UTF-8 $ELIXIR --eval '"-I#{System.get_env("ERL_EI_INCLUDE_DIR", "#{to_string(:code.root_dir)}/usr/include")}" |> IO.puts')]) fi AC_MSG_RESULT([$ERL_CFLAGS]) AC_MSG_CHECKING([setting ERL_LDFLAGS]) if test "x$ERL_LDFLAGS" = "x"; then AC_SUBST([ERL_LDFLAGS], [$(LC_ALL=en_US.UTF-8 $ELIXIR --eval '"-L#{System.get_env("ERL_EI_LIBDIR", "#{to_string(:code.root_dir)}/usr/lib")}" |> IO.puts')]) fi AC_MSG_RESULT([$ERL_LDFLAGS]) working_foundation=no working_nslog=no if test "x$all_mac" = "xyes"; then AC_MSG_CHECKING([whether Foundation Framework exists]) cat>_framework.m<<EOF #import <Foundation/Foundation.h> int main() { return 0; } EOF if xcrun clang _framework.m -o _framework > /dev/null 2>&1 && ./_framework > /dev/null 2>&1 ; then working_foundation=yes fi rm -f _framework.m _framework.o _framework AC_MSG_RESULT([$working_foundation]) AC_MSG_CHECKING([whether NSLog works]) cat>_nslog.m<<EOF #import <Foundation/Foundation.h> int main() { NSLog(@"hello world"); return 0; } EOF if xcrun clang _nslog.m -o _nslog -framework Foundation > /dev/null 2>&1 && ./_nslog > /dev/null 2>&1 ; then working_nslog=yes fi rm -f _nslog.m _nslog.o _nslog AC_MSG_RESULT([$working_nslog]) fi AM_CONDITIONAL([EXIST_FOUNDATION], [test "x$working_foundation" = "xyes"]) AM_CONDITIONAL([WORK_NSLOG], [test "x$working_nslog" = "xyes"]) LT_INIT() AC_CONFIG_FILES([Makefile]) AC_OUTPUT追加分を説明します。
working_foundation=no
とworking_nslog=no
でそれぞれのフラグを初期化します。if test "x$all_mac" = "xyes"; then
で,ビルド時,ホスト,ターゲットが全てmacOSのときのみ動作するようにします。AC_MSG_CHECKING([whether Foundation Framework exists])
でFoundationが存在するかチェックしますと表示します。- 次の
cat
から2つ目のEOF
までがテストするプログラムコードです。- 次の
if
でこのプログラムコードをコンパイルして実行し,正常終了することを確認します。標準出力とエラー出力をまとめてリダイレクトする点に注意してください。rm
で生成したファイルを削除します。AC_MSG_RESULT([$working_foundation])
で検証結果を表示します。- 同様に
NSLog
についても動作確認します。AM_CONDITIONAL
で判定結果をMakefile.am
で利用できるようにします。それぞれ,EXIST_FOUNDATION
とWORK_NSLOG
で真偽値を取り出せるようにしています。Foundationと
NSLog
は標準の機能なので,存在をチェックしてコードに反映するのはナンセンスだと思いますが,例としてやってみましょう。Makefile.amAUTOMAKE_OPTIONS = subdir-objects ACLOCAL_AMFLAGS = -I m4 if EXIST_FOUNDATION OBJC_FLAGS += -DEXIST_FOUNDATION endif if WORK_NSLOG OBJC_FLAGS += -DWORK_NSLOG endif lib_LTLIBRARIES = priv/libnif.la priv_libnif_la_SOURCES = native/libnif.c if ALLOSX priv_libnif_la_LIBADD = $(LIBOBJS) native/caller.lo native/caller.lo: native/caller.m native/caller.h $(LIBTOOL) --mode=compile xcrun clang -c $(OBJC_FLAGS) $(CFLAGS) -o $@ $< endif if ALLOSX priv_libnif_la_CFLAGS = -DALLOSX $(CFLAGS) $(ERL_CFLAGS) else priv_libnif_la_CFLAGS = $(CFLAGS) $(ERL_CFLAGS) endif priv_libnif_la_LDFLAGS = $(LDFLAGS) $(ERL_LDFLAGS) -shared -module -avoid-version -export-dynamic追加分の説明です。
if EXIST_FOUNDATION
からendif
まではEXIST_FOUNDATION
が真の時にOBJC_FLAGS
に-DEXIST_FOUNDATION
を追加することで,マクロEXIST_FOUNDATION
を定義しています。OBJC_FLAGS
の行をインデントしない点に注意してください。インデントすると変数の更新が読まれなくなってしまいます。- 同様に
if WORK_NSLOG
からendif
まではWORK_FOUNDAION
が真の時にOBJC_FLAGS
に-DWORK_NSLOG
を追加しています。caller.m#ifdef EXIST_FOUNDATION #import <Foundation/Foundation.h> #endif #import "caller.h" void caller() { #ifdef WORK_NSLOG NSLog(@"Hello world from Objective-C."); #endif }マクロ
EXIST_FOUNDATION
とWORK_NSLOG
が定義されているかをみて,それぞれ#import <Foundation/Foundation.h>
とNSLog(...)
をスイッチしています。Objective-CからSwiftのコードを呼び出す
Swift 5.3のコードをObjective-Cから呼び出す方法で既に紹介しましたが,Autoconfに対応させましょう。
次のようなSwiftのコードを呼び出します。このコードの出典はhttps://docs.swift.org/swift-book/LanguageGuide/Methods.html です。
native/ExampleClass.swiftimport Foundation @objc class ExampleClass: NSObject { var count = 0 @objc func increment() { count += 1 NSLog("Hello world from Swift.") } @objc func increment(by amount: Int) { count += amount } @objc func reset() { count = 0 } }Objective-Cから呼び出せるようにするためには,次の2つのことを行います。
import Foundation
として,NSObject
から派生するようにクラスを定義する@objc
をクラスと,Objective-Cから呼び出したいメソッドに付記するこのようなクラスを足がかりとして,任意のSwiftコードを呼び出せば良いというわけです。
Objective-Cの次のように変更します。
native/caller.m#import <Foundation/Foundation.h> #import "ExampleClass-Swift.h" #import "caller.h" void caller() { ExampleClass *obj = [[ExampleClass alloc] init]; [obj increment]; NSLog(@"Hello world from Objective-C."); }ポイントは次のとおりです。
- クラス名が
ExampleClass
である場合には,import "ExampleClass-Swift.h"
とする(クラス名に-Swift.h
をつけたヘッダファイルをインポートする)- あとはSwiftのコードをObjective-Cに読み替えて呼び出す。
configure.ac
を次のようにします。configure.acdnl Process this file with autoconf to produce a configure script AC_INIT([priv/.libs/libnif.so], [1.0]) AC_CANONICAL_BUILD AC_CANONICAL_HOST AC_CANONICAL_TARGET AC_CONFIG_MACRO_DIRS([m4]) AM_INIT_AUTOMAKE([-Wall -Werror foreign]) AC_ARG_VAR([ELIXIR], [Elixir]) AC_ARG_VAR([ERL_EI_INCLUDE_DIR], [ERL_EI_INCLUDE_DIR]) AC_ARG_VAR([ERL_EI_LIBDIR], [ERL_EI_LIBDIR]) AC_ARG_VAR([CROSSCOMPILE], [CROSSCOMPILE]) AC_ARG_VAR([ERL_CFLAGS], [ERL_CFLAGS]) AC_ARG_VAR([ERL_LDFLAGS], [ERL_LDFLAGS]) AC_ARG_VAR([OBJC_FLAGS], [OBJC_FLAGS]) AC_ARG_VAR([SWIFT_FLAGS], [SWIFT_FLAGS]) AC_PROG_CC build_linux=no build_mac=no all_mac=no case "${host_os}" in linux*) build_linux=yes ;; cygwin*|mingw*) AC_MSG_ERROR([OS $host_os on Windows is not supported]) ;; darwin*) case "${build_os}" in darwin*) case "${target_os}" in darwin*) all_mac=yes AC_PATH_PROG(XCRUN, xcrun) ;; *) ;; esac ;; *) ;; esac build_mac=yes ;; *) AC_MSG_ERROR([OS $host_os is not suppurted]) ;; esac AM_CONDITIONAL([LINUX], [test "x$build_linux" = "xyes"]) AM_CONDITIONAL([OSX], [test "x$build_mac" = "xyes"]) AM_CONDITIONAL([ALLOSX], [test "x$all_mac" = "xyes"]) AM_PROG_AR AC_PATH_PROG(ELIXIR, $ELIXIR, elixir) AC_MSG_CHECKING([setting ERL_EI_INCLUDE_DIR]) if test "x$ERL_EI_INCLUDE_DIR" = "x"; then AC_SUBST([ERL_EI_INCLUDE_DIR], [$(LC_ALL=en_US.UTF-8 $ELIXIR --eval ':code.root_dir |> to_string() |> Kernel.<>("/usr/include") |> IO.puts')]) fi AC_MSG_RESULT([$ERL_EI_INCLUDE_DIR]) AC_MSG_CHECKING([setting ERL_EI_LIBDIR]) if test "x$ERL_EI_LIBDIR" = "x"; then AC_SUBST([ERL_EI_LIBDIR], [$(LC_ALL=en_US.UTF-8 $ELIXIR --eval ':code.root_dir |> to_string() |> Kernel.<>("/usr/lib") |> IO.puts')]) fi AC_MSG_RESULT([$ERL_EI_LIBDIR]) AC_MSG_CHECKING([setting ERL_CFLAGS]) if test "x$ERL_CFLAGS" = "x"; then AC_SUBST([ERL_CFLAGS], [$(LC_ALL=en_US.UTF-8 $ELIXIR --eval '"-I#{System.get_env("ERL_EI_INCLUDE_DIR", "#{to_string(:code.root_dir)}/usr/include")}" |> IO.puts')]) fi AC_MSG_RESULT([$ERL_CFLAGS]) AC_MSG_CHECKING([setting ERL_LDFLAGS]) if test "x$ERL_LDFLAGS" = "x"; then AC_SUBST([ERL_LDFLAGS], [$(LC_ALL=en_US.UTF-8 $ELIXIR --eval '"-L#{System.get_env("ERL_EI_LIBDIR", "#{to_string(:code.root_dir)}/usr/lib")}" |> IO.puts')]) fi AC_MSG_RESULT([$ERL_LDFLAGS]) working_foundation=no working_nslog=no if test "x$all_mac" = "xyes"; then AC_MSG_CHECKING([whether Foundation Framework exists]) cat>_framework.m<<EOF #import <Foundation/Foundation.h> int main() { return 0; } EOF if xcrun clang _framework.m -o _framework > /dev/null 2>&1 && ./_framework > /dev/null 2>&1 ; then working_foundation=yes fi rm -f _framework.m _framework.o _framework AC_MSG_RESULT([$working_foundation]) AC_MSG_CHECKING([whether NSLog works]) cat>_nslog.m<<EOF #import <Foundation/Foundation.h> int main() { NSLog(@"hello world"); return 0; } EOF if xcrun clang _nslog.m -o _nslog -framework Foundation > /dev/null 2>&1 && ./_nslog > /dev/null 2>&1 ; then working_nslog=yes fi rm -f _nslog.m _nslog.o _nslog AC_MSG_RESULT([$working_nslog]) fi AM_CONDITIONAL([EXIST_FOUNDATION], [test "x$working_foundation" = "xyes"]) AM_CONDITIONAL([WORK_NSLOG], [test "x$working_nslog" = "xyes"]) LT_INIT() AC_CONFIG_FILES([Makefile]) AC_OUTPUT変更点は次のとおりです。
AC_ARG_VAR([SWIFT_FLAGS], [SWIFT_FLAGS])
を追加する本題の
Makefile.am
を次のようにします。Makefile.amAUTOMAKE_OPTIONS = subdir-objects ACLOCAL_AMFLAGS = -I m4 lib_LTLIBRARIES = priv/libnif.la priv_libnif_la_SOURCES = native/libnif.c if ALLOSX priv_libnif_la_LIBADD = $(LIBOBJS) native/caller.lo native/ExampleClass.lo native/caller.lo: native/caller.m native/caller.h native/ExampleClass-Swift.h $(LIBTOOL) --mode=compile xcrun clang -c $(OBJC_FLAGS) $(CFLAGS) -o $@ $< native/ExampleClass.lo: native/ExampleClass.swift $(LIBTOOL) --mode=compile ./swiftc_wrapper $(SWIFT_FLAGS) -emit-object -parse-as-library $< -o $@ native/ExampleClass-Swift.h: native/ExampleClass.swift xcrun swiftc $(SWIFT_FLAGS) $< -emit-objc-header -emit-objc-header-path $@ endif if ALLOSX priv_libnif_la_CFLAGS = -DALLOSX $(CFLAGS) $(ERL_CFLAGS) else priv_libnif_la_CFLAGS = $(CFLAGS) $(ERL_CFLAGS) endif if ALLOSX priv_libnif_la_LDFLAGS = $(LDFLAGS) $(ERL_LDFLAGS) -shared -module -avoid-version -export-dynamic -L`xcrun --show-sdk-path`/usr/lib/swift -undefined dynamic_lookup else priv_libnif_la_LDFLAGS = $(LDFLAGS) $(ERL_LDFLAGS) -shared -module -avoid-version -export-dynamic endif変更点は次のとおりです。
priv_libnif_la_LIBADD
にnative/ExampleClass.lo
を加えるnative/caller.lo
を生成するルールの依存関係にnative/ExampleClass-Swift.h
を加えるnative/ExampleClass.lo
を生成するルールを加える
- 依存ファイルは
native/ExampleClass.swift
- Libtoolで
swiftc
を呼び出すのですが,そのまま呼び出すとオプションのエラーになるので,ラッパーswiftc_wrapper
を介して呼び出す(後述)-emit-object
をつけることでオブジェクトファイルを生成する-parse-as-library
をつけることでライブラリとしてリンクできるようにするnative/ExampleClass-Swift.h
を生成するルールを加える
xcrun swiftc
でSwiftのコンパイラを呼び出す-emit-objc-header
でObjective-Cのヘッダファイルを生成させる-emit-objc-header-path
で生成先を指定する- (残課題)
ExampleClass{,.swift{doc,module,sourceinfo}}
を生成してしまうのだけど,生成を抑制する方法がわからないALLOSX
のとき,priv_libnif_la_LDFLAGS
に以下を加えることで,Swiftのライブラリをリンクする-L`xcrun --show-sdk-path`/usr/lib/swift -undefined dynamic_lookup
swiftc_wrapper
は次のようなシェルスクリプトです。#!/bin/sh echo "xcrun swiftc $@" | sed -e 's/-fno-common//g' | sed -e 's/-DPIC//g' > _swiftc_wrapper chmod +x _swiftc_wrapper ./_swiftc_wrapper rm _swiftc_wrapperやっていることは,Libtoolがコンパイルモードでコンパイラを起動する時につけるオプションである
-fno-common
と-DPIC
はswiftc
では認識されないので,外す,ということです。もうちょっとスマートに書けそうに思います。良い方法があったら教えてください。
以上で
mix clean
とiex -S mix
を実行してコンパイルエラーがないことを確認してください。さらに次のように実行すると,SwiftとObjective-CからそれぞれNSLog
でメッセージを表示してくれるはずです。iex(1)> SwiftElixirTest.test 2020-12-01 06:45:06.883 beam.smp[97578:5144991] Hello world from Swift. 2020-12-01 06:45:06.883 beam.smp[97578:5144991] Hello world from Objective-C. :ok iex(2)>Linuxで実行すると次のようになります。
iex(1)> SwiftElixirTest.test {:error, :not_implemented} iex(2)>Foundation と
NSLog
の動作確認(Swift)Foundationと
NSLog
の動作確認をSwiftでも行いましょう。configure.acdnl Process this file with autoconf to produce a configure script AC_INIT([priv/.libs/libnif.so], [1.0]) AC_CANONICAL_BUILD AC_CANONICAL_HOST AC_CANONICAL_TARGET AC_CONFIG_MACRO_DIRS([m4]) AM_INIT_AUTOMAKE([-Wall -Werror foreign]) AC_ARG_VAR([ELIXIR], [Elixir]) AC_ARG_VAR([ERL_EI_INCLUDE_DIR], [ERL_EI_INCLUDE_DIR]) AC_ARG_VAR([ERL_EI_LIBDIR], [ERL_EI_LIBDIR]) AC_ARG_VAR([CROSSCOMPILE], [CROSSCOMPILE]) AC_ARG_VAR([ERL_CFLAGS], [ERL_CFLAGS]) AC_ARG_VAR([ERL_LDFLAGS], [ERL_LDFLAGS]) AC_ARG_VAR([OBJC_FLAGS], [OBJC_FLAGS]) AC_ARG_VAR([SWIFT_FLAGS], [SWIFT_FLAGS]) AC_PROG_CC build_linux=no build_mac=no all_mac=no case "${host_os}" in linux*) build_linux=yes ;; cygwin*|mingw*) AC_MSG_ERROR([OS $host_os on Windows is not supported]) ;; darwin*) case "${build_os}" in darwin*) case "${target_os}" in darwin*) all_mac=yes AC_PATH_PROG(XCRUN, xcrun) ;; *) ;; esac ;; *) ;; esac build_mac=yes ;; *) AC_MSG_ERROR([OS $host_os is not suppurted]) ;; esac AM_CONDITIONAL([LINUX], [test "x$build_linux" = "xyes"]) AM_CONDITIONAL([OSX], [test "x$build_mac" = "xyes"]) AM_CONDITIONAL([ALLOSX], [test "x$all_mac" = "xyes"]) AM_PROG_AR AC_PATH_PROG(ELIXIR, $ELIXIR, elixir) AC_MSG_CHECKING([setting ERL_EI_INCLUDE_DIR]) if test "x$ERL_EI_INCLUDE_DIR" = "x"; then AC_SUBST([ERL_EI_INCLUDE_DIR], [$(LC_ALL=en_US.UTF-8 $ELIXIR --eval ':code.root_dir |> to_string() |> Kernel.<>("/usr/include") |> IO.puts')]) fi AC_MSG_RESULT([$ERL_EI_INCLUDE_DIR]) AC_MSG_CHECKING([setting ERL_EI_LIBDIR]) if test "x$ERL_EI_LIBDIR" = "x"; then AC_SUBST([ERL_EI_LIBDIR], [$(LC_ALL=en_US.UTF-8 $ELIXIR --eval ':code.root_dir |> to_string() |> Kernel.<>("/usr/lib") |> IO.puts')]) fi AC_MSG_RESULT([$ERL_EI_LIBDIR]) AC_MSG_CHECKING([setting ERL_CFLAGS]) if test "x$ERL_CFLAGS" = "x"; then AC_SUBST([ERL_CFLAGS], [$(LC_ALL=en_US.UTF-8 $ELIXIR --eval '"-I#{System.get_env("ERL_EI_INCLUDE_DIR", "#{to_string(:code.root_dir)}/usr/include")}" |> IO.puts')]) fi AC_MSG_RESULT([$ERL_CFLAGS]) AC_MSG_CHECKING([setting ERL_LDFLAGS]) if test "x$ERL_LDFLAGS" = "x"; then AC_SUBST([ERL_LDFLAGS], [$(LC_ALL=en_US.UTF-8 $ELIXIR --eval '"-L#{System.get_env("ERL_EI_LIBDIR", "#{to_string(:code.root_dir)}/usr/lib")}" |> IO.puts')]) fi AC_MSG_RESULT([$ERL_LDFLAGS]) working_foundation=no working_nslog=no if test "x$all_mac" = "xyes"; then AC_MSG_CHECKING([whether Foundation Framework exists in Objective C]) cat>_framework.m<<EOF #import <Foundation/Foundation.h> int main() { return 0; } EOF if xcrun clang _framework.m -o _framework > /dev/null 2>&1 && ./_framework > /dev/null 2>&1 ; then AC_MSG_CHECKING([and Swift]) cat>_framework.swift<<EOF import Foundation import Darwin exit(0) EOF if xcrun swiftc _framework.swift -o _framework > /dev/null 2>&1 && ./_framework > /dev/null 2>&1 ; then working_foundation=yes fi fi rm -f _framework.swift _framework.m _framework.o _framework AC_MSG_RESULT([$working_foundation]) AC_MSG_CHECKING([whether NSLog works in Objective-C]) cat>_nslog.m<<EOF #import <Foundation/Foundation.h> int main() { NSLog(@"hello world"); return 0; } EOF if xcrun clang _nslog.m -o _nslog -framework Foundation > /dev/null 2>&1 && ./_nslog > /dev/null 2>&1 ; then AC_MSG_CHECKING([and Swift]) cat>_nslog.swift<<EOF import Foundation import Darwin NSLog("hello world") exit(0) EOF if xcrun swiftc _nslog.swift -o _nslog > /dev/null 2>&1 && ./_nslog > /dev/null 2>&1 ; then working_nslog=yes fi fi rm -f _nslog.swift _nslog.m _nslog.o _nslog AC_MSG_RESULT([$working_nslog]) fi AM_CONDITIONAL([EXIST_FOUNDATION], [test "x$working_foundation" = "xyes"]) AM_CONDITIONAL([WORK_NSLOG], [test "x$working_nslog" = "xyes"]) LT_INIT() AC_CONFIG_FILES([Makefile]) AC_OUTPUTMakefile.amAUTOMAKE_OPTIONS = subdir-objects ACLOCAL_AMFLAGS = -I m4 if EXIST_FOUNDATION OBJC_FLAGS += -DEXIST_FOUNDATION SWIFT_FLAGS += -DEXIST_FOUNDATION endif if WORK_NSLOG OBJC_FLAGS += -DWORK_NSLOG SWIFT_FLAGS += -DWORK_NSLOG endif lib_LTLIBRARIES = priv/libnif.la priv_libnif_la_SOURCES = native/libnif.c if ALLOSX priv_libnif_la_LIBADD = $(LIBOBJS) native/caller.lo native/ExampleClass.lo native/caller.lo: native/caller.m native/caller.h native/ExampleClass-Swift.h $(LIBTOOL) --mode=compile xcrun clang -c $(OBJC_FLAGS) $(CFLAGS) -o $@ $< native/ExampleClass.lo: native/ExampleClass.swift $(LIBTOOL) --mode=compile ./swiftc_wrapper $(SWIFT_FLAGS) -emit-object -parse-as-library $< -o $@ native/ExampleClass-Swift.h: native/ExampleClass.swift xcrun swiftc $(SWIFT_FLAGS) $< -emit-objc-header -emit-objc-header-path $@ endif if ALLOSX priv_libnif_la_CFLAGS = -DALLOSX $(CFLAGS) $(ERL_CFLAGS) else priv_libnif_la_CFLAGS = $(CFLAGS) $(ERL_CFLAGS) endif if ALLOSX priv_libnif_la_LDFLAGS = $(LDFLAGS) $(ERL_LDFLAGS) -shared -module -avoid-version -export-dynamic -L`xcrun --show-sdk-path`/usr/lib/swift -undefined dynamic_lookup else priv_libnif_la_LDFLAGS = $(LDFLAGS) $(ERL_LDFLAGS) -shared -module -avoid-version -export-dynamic endifnative/ExampleClass.swift#if EXIST_FOUNDATION import Foundation #endif @objc class ExampleClass: NSObject { var count = 0 @objc func increment() { count += 1 #if WORK_NSLOG NSLog("Hello world from Swift.") #endif } @objc func increment(by amount: Int) { count += amount } @objc func reset() { count = 0 } }だいたいObjective-Cの場合と同様ですが,いくつか違いがあります。
swiftc
では-framework Foundation
は不要です。- Cでの
#ifdef
はSwiftでは#if
です。おわりに
この記事ではElixirからObjective-CやSwift 5.3のコードを呼び出す方法について詳説しました。またAutotoolsを使ってObjective-CやSwiftのヘッダファイルや関数が存在するかどうかを確認する方法も示しています。
将来課題としては,Autotoolsを使うとビルドに時間がかかるようになってくるので,
make -j
による並列コンパイルをしたり,必要な時だけautoreconf
や./configure
をするように改めたいのと,もっと長期的にはAutotoolsの代わりをするElixirベースのビルドツールを構築したいなと思っています。あと,M1 Macだったときにはユニバーサルバイナリを生成するようにしてみたいですね。
- ユニバーサルバイナリの生成方法はBuilding a Universal macOS Binaryに書かれています。
- また,
system_profiler SPSoftwareDataType
とするとSystem Version
の項目にmacOSのバージョンが出ます。以上を利用して,Big Surは macOS 11.0なので,それ以降だったらx86_64とarm64のユニバーサルバイナリを生成するというロジックでも良いかと思います。この方法はまた後日試してみたいと思います。
明日のElixir Advent Calendar 2020 11日目の記事は @pojiro さんの「Elixirで並行コマンド実行サーバーを作ったら感動した話」です。よろしくお願いします。
本研究成果は、科学技術振興機構研究成果展開事業研究成果最適展開支援プログラム A-STEP トライアウト JPMJTM20H1 の支援を受けた。
- 投稿日:2020-12-04T15:32:41+09:00
Apple Silicon M1チップ搭載Mac (Big Sur) に Elixir / Erlang をクリーンインストールする〜Elixir/Pelemayマイクロベンチマーク結果もあるよ!(2020.12.4現在版)
この記事はElixir Advent Calendar 2020の6日目です。
昨日は @ShozF さんの「Elixir練習帳: .npyファイルの中を覗く」でした。
さて,今日はおまちかね,Apple Silicon M1チップ搭載のMacにElixirとErlangをクリーンインストールしてみたという記事です。Elixir/Pelemayのマイクロベンチマークも走らせてみました。そのパフォーマンスの高さに驚いてくださいませ。
インストール状況について,結論から言うと
インストール方法 Rosetta 2 ARM ネイティブ Homebrew ○ ○ asdf
△? ○ kerl
○? × ?がついているところは2020年12月4日現在で完全には確認できていないところです。そのうち追って確認します。
追記(2020/12/7): ARMネイティブの時の
kerl
でのインストールがうまく動かないことがわかりました。引き続き検証します。追記(2020/12/7): ARMネイティブで
asdf
でインストールした場合,Rosetta 2モードでターミナルを起動しても,Elixirを実行できることがわかりました。追記(2020/12/8): しかし↑この場合(ARMネイティブで
asdf
をインストールしてRosetta2モードでターミナルを起動した場合),今まで知られている方法でNIFをコンパイルして実行しようとすると,x86_64のバイナリでコンパイル・リンクしようとするため,不一致のためNIFをロード・実行できないという問題があることがわかりました。HomebrewでのElixirインストール手順
- Xcodeをインストールする
- Xcodeを起動してコンポーネントをインストールする
- ターミナルで
xcode-select --install
を実行する- Homebrew をインストールする (Rosetta2の場合とArmネイティブの場合)
brew install elixir
asdf
でのElixirインストール手順下記の記事通りです。
https://qiita.com/zacky1972/items/ad2545fe8414bbd177a0
注意点としては,原理上,Rosetta 2 モードと ARM ネイティブモードを両立できないという点です。
asdf
の抜本的な改造が必要でしょうね。というわけで,issue 書きました。 https://github.com/asdf-vm/asdf/issues/834追記(2020/12/8): ↑issueに否定的なコメントがつきましたね。理解はできます。
kerl
でのElixirインストール手順下記の記事通りです。
https://qiita.com/zacky1972/items/ad2545fe8414bbd177a0
ARMネイティブモードの場合,
export KERL_CONFIGURE_OPTIONS="--with-ssl=/opt/homebrew/opt/openssl@1.1 --enable-darwin-64bit"
とします。
kerl build 23.1.4 23.1.4_Rosetta2
,kerl build 23.1.4 23.1.4_ARM
みたいにすると,Rosetta 2 モードと ARM ネイティブモードを共存できます。Xcodeを起動するときの注意点
https://zenn.dev/paraches/articles/m1-xcode-rosetta2 を参照のこと
要はリターンキーを押せば先に進みます。
Rosetta2 の Homebrew のインストール方法
- アプリケーション/ユーティリティ フォルダを開いてターミナルの「情報を見る」を開きます。
- 「Rosettaを使用して開く」にチェックを入れます。
- ターミナルを起動します。
- あとは通常のHomebrewのインストール手順です。
通常と同じく
/usr/local
配下にインストールされます。ちなみに,「Rosettaを使用して開く」にチェックを入れていても,
ssh
で外部からログインしたときには,そのシェルではRosettaは有効にならず,ARMネイティブモードになります。ARMネイティブのHomebrewのインストール方法
https://github.com/mikelxc/Workarounds-for-ARM-mac
- アプリケーション/ユーティリティ フォルダを開いてターミナルの「情報を見る」を開きます。
- 「Rosettaを使用して開く」のチェックを外します。
- ターミナルを起動します。
cd ~
mkdir homebrew && curl -L https://github.com/Homebrew/brew/tarball/master | tar xz --strip 1 -C homebrew
sudo mv homebrew /opt/homebrew
cd /opt/homebrew/bin
./brew update
- .zshrc に
export PATH="/opt/homebrew/bin:$PATH"
を記述するsource .zshrc
通常と異なり,
/opt/homebrew
配下にインストールされます。おまけ: ベンチマーク結果
次のような自作のベンチマークプログラムを走らせてみました。
https://github.com/zeam-vm/pelemay_sample
Pelemay 0.0.14になってARMネイティブモードのM1 Macで動くようになりました。
参考: iMac Pro 2017 (CPU/GPU全部盛り) での実行結果
## FloatMultBench benchmark name iterations average time Pelemay 50000 38.77 µs/op Enum 10000 233.19 µs/op Flow 500 3368.38 µs/op ## LogisticMapBench benchmark name iterations average time Pelemay 5000 624.14 µs/op Enum 1000 2386.87 µs/op Flow 500 5600.73 µs/op ## StringReplaceBench benchmark name iterations average time Pelemay String.replace 10000000 0.80 µs/op Enum String.replace 1000000 2.61 µs/op Flow String.replace 1000 1999.50 µs/opMac mini (M1, 2020) ARM モードでの実行結果
benchmark name iterations average time Pelemay 100000 28.60 µs/op Enum 10000 119.89 µs/op Flow 1000 1021.03 µs/op ## LogisticMapBench benchmark name iterations average time Pelemay 5000 377.24 µs/op Enum 2000 897.06 µs/op Flow 1000 2265.86 µs/op ## StringReplaceBench benchmark name iterations average time Pelemay String.replace 10000000 0.66 µs/op Enum String.replace 1000000 1.45 µs/op Flow String.replace 10000 252.52 µs/opMac mini (M1, 2020) Rosetta 2 モードでの実行結果
Pelemay が,たとえバージョン0.0.14であっても,バグで動かなかったので,削除してあります。issueを立ててありますので,詳細をご覧になりたい方はどうぞ。
https://github.com/zeam-vm/pelemay/issues/154## FloatMultBench benchmark name iterations average time Enum 10000 278.17 µs/op Flow 1000 1725.31 µs/op ## LogisticMapBench benchmark name iterations average time Enum 1000 2272.93 µs/op Flow 500 4115.19 µs/op ## StringReplaceBench benchmark name iterations average time Enum String.replace 1000000 2.39 µs/op Flow String.replace 5000 388.05 µs/op結果
いずれも ARMモードの Mac mini (M1, 2020) の方が iMac Pro 2017 よりかなり高速でした。Pelemayの速度向上は大体C言語によるネイティブのコードの速度向上比と同程度と考えられます。一方,Enumの速度向上はシングルコアで動作する標準的なインタプリタの場合の速度向上比と考えられます。Flowの速度向上は,マルチコアで動作する標準的なインタプリタの場合の速度向上比と考えられます。シングルコア性能も高いですが,とくにマルチコア性能は高いですね!
Pelemay iMac Pro 2017 Mac mini (M1, 2020) ARM 倍率 整数演算 624.14 µs/op 377.24 µs/op 1.65倍 浮動小数点数演算 38.77 µs/op 28.60 µs/op 1.36倍 文字列置換 0.80 µs/op 0.66 µs/op 1.21倍
Enum iMac Pro 2017 Mac mini (M1, 2020) ARM 倍率 整数演算 2386.87 µs/op 897.06 µs/op 2.66倍 浮動小数点数演算 233.19 µs/op 119.89 µs/op 1.95倍 文字列置換 2.61 µs/op 1.45 µs/op 1.80倍
Flow iMac Pro 2017 Mac mini (M1, 2020) ARM 倍率 整数演算 5600.73 µs/op 2265.86 µs/op 2.47倍 浮動小数点数演算 3368.38 µs/op 1021.03 µs/op 3.30倍 文字列置換 1999.50 µs/op 252.52 µs/op 7.92倍 Rosetta 2 モードの Mac mini (M1, 2020) と iMac Pro 2017 はEnumでほぼ互角,FlowではMac miniの方が速いです。この場合も,マルチコアであるFlowの場合の方が速度向上比が高かったです。
Enum iMac Pro 2017 Mac mini (M1, 2020) Rosetta 2 倍率 整数演算 2386.87 µs/op 2272.93 µs/op 1.05倍 浮動小数点数演算 233.19 µs/op 278.17 µs/op 0.838倍 文字列置換 2.61 µs/op 2.39 µs/op 1.09倍
Flow iMac Pro 2017 Mac mini (M1, 2020) Rosetta 2 倍率 整数演算 5600.73 µs/op 4115.19 µs/op 1.36倍 浮動小数点数演算 3368.38 µs/op 1725.31 µs/op 1.95倍 文字列置換 1999.50 µs/op 388.05 µs/op 5.15倍 Rosetta 2 より ARM ネイティブは1.6〜2.5倍程度高速であると言えそうです。
Mac mini (M1, 2020) Enum Rosetta 2 ARM 倍率 整数演算 2272.93 µs/op 897.06 µs/op 2.53倍 浮動小数点数演算 278.17 µs/op 119.89 µs/op 2.32倍 文字列置換 2.39 µs/op 1.45 µs/op 1.65倍
Mac mini (M1, 2020) Flow Rosetta 2 ARM 倍率 整数演算 4115.19 µs/op 2265.86 µs/op 1.95倍 浮動小数点数演算 1725.31 µs/op 1021.03 µs/op 1.69倍 文字列置換 388.05 µs/op 252.52 µs/op 1.54倍 おわりに
明日のElixir Advent Calendar 2020 7日目の記事は @koyo-miyamura さんです。よろしくお願いします。
本研究成果は、科学技術振興機構研究成果展開事業研究成果最適展開支援プログラム A-STEP トライアウト JPMJTM20H1 の支援を受けた。
- 投稿日:2020-12-04T11:22:16+09:00
デザイナーがM1のMacBookAirで満足できるか
土曜日の昼下がり、玄関先でごそっと物音をたてて、金属のラックが少し揺れた。
置き配用の金属ラックの3段目、飾り気のない段ボールをひとつ。その中には見覚えがあるソリッドの立った白い箱があり、それを右隅からビニールを心地剥がすと前回の記憶が蘇る。──正直そこまで期待していなかった
世間的には「奇跡的だ」「魔法の絨毯だ」とブログ、YouTube界隈で過剰に騒ぎ立てて、疑心暗鬼になっていたが、旧MacBookから移行アシスタントが終了し、10分後にはその考えが覆った。──感想は「テスラ・モーターズ」
起動速度、静音性、バッテリー性能、発熱性、どれをとっても新しく感じ、触った感想は「テスラ」(乗ったことないけど)。昨日まで使用していたMacはリッター15kmのワゴンだと感じた。もちろん、Rosetta2を還してのアプリケーションも多く、ユニバーサルバージョンにまだ移行していないアプリでこの速度だと半年後がさらに楽しみになってくる。──Apple M1チップ
このプロダクトはAppleが独自で開発した「M1チップ」に集約されていると言っても過言ではない。(というか、他はほとんど変わっていない)プロセッサ、I/O、セキュリティ、メモリなどを1枚のシステムオンチップ(SoC)に集めたのがM1チップで効率が上がり、驚異的な性能を発揮している。またiPhoneやiPadに搭載されているプロセッサーが同じということもあり、iPhoneやiPadのアプリケーションも動かせるのも大きい。
──飛ぶように早い
驚くのが2019年に販売された、ほとんどのMacのよりも早いということ。まず初速度が違う。これはユニファイドメモリアーキテクチャ(UMA)から来たもので、専用パッケージの中にある1つのプールに入れて、SoCにあるデータにアクセスでき、複数のメモリプールの間でデータをコピーする必要がなくなったためだと思われる。
──静寂そのものの
筆者がこのMacBookAirに「テスラ」のメタファを用いるのはファンそのものがない静寂性を上げている。どれだけ負荷をかけた作業をしていても一切の音がしないのだ。想像して欲しい。今持っているiphoneにyoutubeなどで負荷をかけると高周波でファンのが鳴り出したら気が狂う。そう考えるとファンがある元のMacには戻れない。
──デザイナーはメイン機として使用できるか
正直、世の中の90%ぐらいのデザイナーは一番安いモデル(12月3日時点で104,800円(税別))で満足できると思う。SSDの容量はクラウドで補完すれば良い。メモリは8GBと心許ない感じがするが、SoCにメモリが統合された「ユニファイドメモリ」が効率的に作用する。たとえスワップしたとしても、SSDの性能が向上でカバーできている。──購入すべきデザイナー(2つ以上該当する場合)
・所有Macのマルチコアベンチマークが3,500以下(https://browser.geekbench.com/mac-benchmarks )
・Macのメモリが8GB以下
・macOS Big Surにアップデートをしている(予定がある)
・20インチ以上のディスプレイを所有している(予定がある)
・すごくお金がある人──プロとアマチュアの境界が消えていく
今まで、Macで「Pro」を所有するというのは、デザイナーの自己顕示欲のようなものだったのは間違いない。30年ほど前、MacintoshはDTPの道を切り開き、デザイナーの道具の境界線を消してきた。それ以前は1mmの線の上に10本の線を烏口で引くことや、写植、活版など知識、技術がなければ、印刷物などに辿り着かなかったから。しかし、今現在では20万円ほどでプロと並ぶほどの道具は揃えられるし、ある程度PCを触れる主婦なら、3日ほどでラクスルへ入稿できる。そういった意味で確実にハードルが下がってきている。そういった中で、デザイナーはこれからどのようなバリューを提供していかなければいけないか、考え直す必要があると思う。