はじめに
apple/container の 1.0.0 がリリースされました🎉
せっかくなので、apple/container 上で Embedded Swift をビルドしてみました
ホスト PC に Swift toolchain も Pico SDK も ARM GCC も入れずに、Raspberry Pi Pico (RP2040) 向けのファームウェアをビルドできます
VS Code Remote-SSH との組み合わせで、コンテナ内の sourcekit-lsp によるリアルタイム診断もできます
リポジトリはこちらです
ビルドする
1. コンテナを起動する
git clone https://github.com/fuziki/apple-container-embedded-swift.git
cd apple-container-embedded-swift
container system start
bash scripts/start-dev-container.sh
初回はイメージのビルドで数分かかります
完了すると ~/.ssh/config に追加するブロックが表示されるので追記します
2. VS Code で接続する
コマンドパレットから Remote-SSH: Connect to Host → pico-dev を選択します
フォルダは /work を開きます(リポジトリがマウントされています)
Swift 拡張(swiftlang.swift)のインストールを促されたらコンテナ側に入れます
3. Configure する
VS Code のターミナルで実行します
bash scripts/configure.sh
ウィンドウを再読み込みすると Main.swift のリアルタイム診断が有効になります
4. ビルドする
Cmd+Shift+B でビルドが走り、./out/swift-app.uf2 が生成されます
5. Pico に書き込む
BOOTSEL を押しながら USB 接続すると RPI-RP2 としてマウントされます
./out/swift-app.uf2 をドラッグすれば LED が点滅します
実装のポイント
Dockerfile
FROM swiftlang/swift:nightly-noble
RUN apt-get update && apt-get install -y --no-install-recommends \
git ninja-build python3 build-essential \
gcc-arm-none-eabi libnewlib-arm-none-eabi \
openssh-server \
&& rm -rf /var/lib/apt/lists/*
RUN pip3 install --break-system-packages cmake
RUN git clone --depth 1 https://github.com/raspberrypi/pico-sdk.git /opt/pico-sdk \
&& cd /opt/pico-sdk && git submodule update --init
ENV PICO_SDK_PATH=/opt/pico-sdk
ベースイメージは nightly を使う
Embedded Swift は Development Snapshot(nightly)の toolchain でしか使えません
release イメージには含まれていないので swiftlang/swift:nightly-noble を使います
CMake は pip で入れる
Ubuntu の apt 版は 3.28 以下で、Embedded Swift のビルドには 3.29 以上が必要です
pip で最新版を入れることで回避しています
Pico SDK はイメージに焼き込む
ホストに clone せず、 /opt/pico-sdk に入れているので、ホスト側には何も残りません
container exec を SSH の ProxyCommand にする
apple/container は今のところ TCP ポートフォワーディングに制限があります
container exec を ProxyCommand にすることで、ポートを開けずに VS Code からコンテナに接続できます
Host pico-dev
HostName pico-dev
User root
ProxyCommand container exec -i pico-dev /usr/sbin/sshd -i
sshd -i は inetd モードで、ポートのかわりに stdin/stdout を通信路として使います
コンテナを再作成しても IP が変わっても、この設定のまま繋がります
VS Code 拡張をホスト側にキャッシュする
コンテナを再作成するたびに拡張をインストールし直すのは手間です
/root/.vscode-server をホスト側にマウントしてキャッシュしています
--volume "$HOME/.cache/pico-dev/vscode-server:/root/.vscode-server"
BridgingHeaderLSP.h:sourcekit-lsp 向けのスタブヘッダ
Pico SDK のヘッダは ARM 向けクロスコンパイラを前提としていて、sourcekit-lsp の内部 clang ではパースエラーになります
そこで Swift から呼ぶ C の関数だけを宣言したスタブを用意して、診断はそちらで行います
// firmware/BridgingHeaderLSP.h
#pragma once
#include <stdint.h>
#include <stdbool.h>
typedef unsigned int uint;
void sleep_ms(uint32_t ms);
void gpio_init(uint gpio);
void gpio_set_dir(uint gpio, bool out);
void gpio_put(uint gpio, bool value);
実際のビルドは本物の SDK ヘッダを使うので、スタブとシグネチャが食い違えばビルドが落ちます
CMakeLists.txt:Embedded Swift に必要な設定
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
set(CMAKE_Swift_COMPILER_TARGET armv6m-none-none-eabi)
# Pico SDK のデフォルトオプションが Swift のコンパイルを邪魔するので退避する
set_target_properties(pico_standard_link PROPERTIES INTERFACE_COMPILE_OPTIONS "")
target_compile_options(pico_standard_link INTERFACE
"$<$<COMPILE_LANGUAGE:C>:SHELL: -ffunction-sections -fdata-sections>")
# ARM GCC の暗黙インクルードパスを Swift コンパイラに渡す
set(SWIFT_INCLUDES)
foreach(dir ${CMAKE_C_IMPLICIT_INCLUDE_DIRECTORIES})
string(CONCAT SWIFT_INCLUDES ${SWIFT_INCLUDES} "-Xcc -I${dir} ")
endforeach()
target_compile_options(swift-blinky PUBLIC "$<$<COMPILE_LANGUAGE:Swift>:SHELL:
-enable-experimental-feature Embedded
-target armv6m-none-none-eabi
-Xcc -mfloat-abi=soft -Xcc -fshort-enums
-Xfrontend -function-sections
-import-bridging-header ${CMAKE_CURRENT_LIST_DIR}/BridgingHeader.h
${SWIFT_INCLUDES} >")
-enable-experimental-feature Embedded で Embedded モードを有効化します
Pico SDK の C ヘッダを Swift に見せるには、ARM GCC の暗黙インクルードパスを -Xcc -I でひとつずつ渡す必要があります
CMAKE_C_IMPLICIT_INCLUDE_DIRECTORIES をループで取り出しているのはそのためです
CMAKE_EXPORT_COMPILE_COMMANDS ON で出力した compile_commands.json を sourcekit-lsp が読むことで、リアルタイム診断が実際のビルドと同じコンパイラ引数で動きます
Docker と比較して
apple/container は Dockerfile がそのまま使えるので、今回のイメージも Docker で書く場合と同じ書き方で済みました
個人的には動作も Docker Desktop より軽い印象を受けました
一方で TCP のポートフォワーディングはまだ弱く、今回 VS Code を container exec 経由で繋ぐひと手間が必要そうでした
今回のような単一コンテナでビルドする用途には十分でした
おわりに
apple/container を使って、ホスト PC に何もインストールせずに Embedded Swift のビルド環境を作れました
Dockerfile でビルド環境をコード化できるのは Docker と同じ感覚で良いですね
今回は Lチカで止まっていますが、次はもう少し実用的なファームウェアを書いてみたいと思います