前言
由於 Objective-C 在非蘋果平台上不是主流語言,不一定每個 GNU/Linux 發行版都會有預編好的 Objective-C 編譯器和 GNUstep 函式庫。編譯 Objective-C 編譯器 (GCC 或 Clang) 和 GNUstep 都是複雜且耗時的任務,難以自行完成。
因應這項議題,本文介紹以 Docker 編譯 Objective-C 程式的方式。此外,由於 Docker Hub 中所分享的 GNUstep 映像檔都很舊了,本文會從 Ubuntu 基底映像檔重新建立 GNUstep 開發環境。
前置作業
使用系統的套件管理程式來安裝 Docker 即可。此處以 openSUSE 為例:
$ sudo zypper install docker
裝完後要啟動 Docker 服務:
$ sudo systemctl start docker
如果很常用 Docker 的話,可自行將 Docker 服務加入開機啟動程式中。
在命令列上直接執行 Docker 容器的方式
我們在先前的文章已經提過了,此處不再重覆。
下載 Ubuntu 映像檔
由於 Docker Hub 上現存的 GNUsetp 映像檔都有點年代了,這裡只下載 Ubuntu 基礎映像檔來用:
$ sudo docker pull ubuntu:20.04
這個映像檔對應到 Ubuntu 20.04 LTS,這是最新版本的長期支援版本,應該可以用好一陣子。
在 Docker 容器中建立 GNUstep 開發環境
使用 -it
參數即可以互動模式進入 Docker 容器:
$ sudo docker run -it ubuntu:20.04 /bin/bash
在 Docker 環境中會自動提升至 root,所以可以直接安裝軟體。參考以下指令來建置 GNUstep 開發環境:
# apt update
# apt upgrade
# apt install gcc g++ gobjc gobjc++ clang gnustep-devel
# exit
將更動過的 Docker 容器提交 (commit) 到新的映像檔:
$ sudo docker ps -l -q
28a2937bd585
$ sudo docker commit 28a2937bd585 gnustep
每次的行程號碼都不一樣,請不要照抄。建議在提交映像檔時另起一個新的映像檔名稱,像是本例的 gnustep
,原本的 Ubuntu 映像檔可以留著建置其他的開發環境。
以 GCC 編譯和執行 Objective-C 容器
由於執行 Docker 容器的指令很長,每次都手動執行不太經濟,這裡將該指令包裝成 shell 命令稿 $HOME/bin/objc :
#!/usr/bin/sh
input=$1
if ! [ -f $input ];
then
sudo docker run --rm --cap-drop=all gnustep /bin/sh -c "gcc $@"
exit $?
fi
shift; # Consume the parameter.
gcc_command="gcc -o a.out ${input} -lobjc -lgnustep-base "
gcc_command+="-I /usr/include/GNUstep -L /usr/lib/GNUstep "
gcc_command+="-fobjc-exceptions -fconstant-string-class=NSConstantString"
sudo docker run --rm -w /app --cap-drop=all -v `pwd`:/app gnustep /bin/sh -c \
"cp /app/$input /tmp; cd /tmp;"\
"${gcc_command};"\
"export LD_LIBRARY_PATH=/usr/lib/GNUstep; ./a.out $@"
注意 GNUstep 在 Ubuntu 上的路徑並非系統標準路徑,所以要設置額外的參數。
雖然 Objective-C++ 相對少見,我們也為其寫了一個 shell 命令稿 $HOME/bin/objc++ :
#!/usr/bin/sh
input=$1
if ! [ -f $input ];
then
sudo docker run --rm --cap-drop=all gnustep /bin/sh -c "g++ $@"
exit $?
fi
shift; # Consume the parameter.
gcc_command="g++ -o a.out ${input} -lobjc -lgnustep-base "
gcc_command+="-I /usr/include/GNUstep -L /usr/lib/GNUstep "
gcc_command+="-fobjc-exceptions -fconstant-string-class=NSConstantString"
sudo docker run --rm -w /app --cap-drop=all -v `pwd`:/app gnustep /bin/sh -c \
"cp /app/$input /tmp; cd /tmp;"\
"${gcc_command};"\
"export LD_LIBRARY_PATH=/usr/lib/GNUstep; ./a.out $@"
其實只是把 gcc
改為 g++
而已。
使用方式如下:
$ objc path/to/source.m
以 Clang 編譯和執行 Objective-C 程式
目前 Objective-C 比較新的語法特性只在 Clang 上有實作,所以本節略為修改先前的 shell 命令稿,改用 clang(1)
來編譯程式。
為了要和 GNUstep 相容,這裡刻意使用 GCC 內附的 libobjc。為了取得 libobjc 的標頭檔路徑,先執行單次 Docker 指令以取得該映像檔內的 GCC 函式庫路徑:
$ sudo docker run gnustep /bin/sh -c "gcc -print-prog-name=cc1 /dev/null"
/usr/lib/gcc/x86_64-linux-gnu/9/cc1
由此可知 GCC 函式庫的標頭檔位於 /usr/lib/gcc/x86_64-linux-gnu/9/include 。只要映像檔沒換,這個路徑是固定的,直接寫死在命令稿內也無妨。
接著修改 objc
命令稿:
#!/usr/bin/sh
input=$1
if ! [ -f $input ];
then
sudo docker run --rm --cap-drop=all gnustep /bin/sh -c "clang $@"
exit $?
fi
shift; # Consume the parameter.
gcc_command="clang -o a.out ${input} -lobjc -lgnustep-base "
gcc_command+="-I /usr/include/GNUstep -L /usr/lib/GNUstep "
gcc_command+="-fobjc-exceptions -fconstant-string-class=NSConstantString"
sudo docker run --rm -w /app --cap-drop=all -v `pwd`:/app gnustep /bin/sh -c \
"cp /app/$input /tmp; cd /tmp;"\
"${gcc_command} -I /usr/lib/gcc/x86_64-linux-gnu/9/include;"\
"export LD_LIBRARY_PATH=/usr/lib/GNUstep; ./a.out $@"
用同樣的概念修改 objc++
命令稿:
#!/usr/bin/sh
input=$1
if ! [ -f $input ];
then
sudo docker run --rm --cap-drop=all gnustep /bin/sh -c "clang++ $@"
exit $?
fi
shift; # Consume the parameter.
gcc_command="clang++ -o a.out ${input} -lobjc -lgnustep-base "
gcc_command+="-I /usr/include/GNUstep -L /usr/lib/GNUstep "
gcc_command+="-fobjc-exceptions -fconstant-string-class=NSConstantString"
sudo docker run --rm -w /app --cap-drop=all -v `pwd`:/app gnustep /bin/sh -c \
"cp /app/$input /tmp; cd /tmp;"\
"${gcc_command} -I /usr/lib/gcc/x86_64-linux-gnu/9/include;"\
"export LD_LIBRARY_PATH=/usr/lib/GNUstep; ./a.out $@"
這時候就可以用 Clang 來編譯 Objective-C 程式。
注意事項
本文所提供的 shell 命令稿以內部使用為主。由於本文所提供的命令稿皆未考慮使用 Docker 的安全事項,勿將這些命令稿用於對外公開的服務程式。
本方案的限制
本方案所用的 shell 命令稿只能用在單一 Objective-C 程式碼,而且該 Objective-C 程式無法讀入外部檔案。如果需要更複雜的使用情境,就要自行修改命令稿。
對於有多個檔案的 Objective-C 專案來說,另外寫 Dockerfile 是比較好的選擇。限於篇幅,不在本文說明撰寫 Dockerfile 的方式。
由於 Clang 和 GCC 對 Objective-C 程式碼不相容,請依專案需求自行選擇所需的 shell 命令稿。