Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
122 changes: 122 additions & 0 deletions .cursor/rules/branch/104-build-command-error.mdc
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
---
description: 104-build-command-errorブランチでの開発時に読み込む
alwaysApply: false
---
ic-wasm optimize 廃止に伴う nicp ビルド移行
===

このブランチで実装することは以下の通りです。

- `nicp` の本番ビルドを、廃止された `ic-wasm optimize O3` から `wasm-opt -O3`(Binaryen)へ移行する。
- 本番ビルドで `wasm-opt` が `PATH` 上にないときは、Nim のコンパイル前に原因と導入要件が分かるエラーを出して失敗させる。
- `nicp new` が生成する `backend/canister.yaml` と、既存 Nim 例の `backend/canister.yaml` を、ネットワークに応じて開発/本番ビルドを選択する同一コマンドへそろえる。
- 開発コンテナと利用者向けの前提条件を、`wasm-opt` を利用できる状態に更新する。

## 進捗

- [x] `ic-wasm` の optimize 廃止と代替コマンドを一次資料で確認する。
- [x] `nicp` のビルド経路、生成テンプレート、既存 examples の設定を調査する。
- [x] 実装方針・詳細設計・検証計画を作成する。
- [x] `nicp` の本番ビルド経路を `wasm-opt -O3` に置換する。
- [x] `wasm-opt` 未導入時の fail-fast エラーを実装する。
- [x] `nicp new` のテンプレートと既存 Nim examples の canister 設定を更新する。
- [x] コンテナ/ドキュメントの Binaryen 導入要件を更新する。
- [x] 単体・結合・回帰確認を実施し、結果を記録する(Docker イメージのビルド確認は Docker 未導入のため未実施)。

## 参考資料

- [ic-wasm README: Optimize (removed)](https://github.com/dfinity/ic-wasm#optimize-removed)
- `ic-wasm` 0.11.0 以降で `optimize` コマンドは廃止された。
- 代替は Binaryen の `wasm-opt -O3 input.wasm -o output.wasm`。
- 近年の Binaryen は `icp:*` メタデータのカスタムセクションを最適化後も保持する。
- `src/cli/nicp_functions/wasm_build.nim`
- `nicp developmentBuild`/`nicp productionBuild` が共有する WASM ビルドパイプライン。
- `src/cli/nicp_functions/new_impl.nim`
- `nicp new` が出力する `backend/canister.yaml` の正本テンプレート。
- `docker/app/develop.Dockerfile`
- 開発コンテナで利用可能にする CLI の定義。

## 調査結果・設計まとめ

### 用語と対象範囲

`examples/**/icp.yaml` は canister 一覧・ネットワーク設定のみを持ち、ビルドコマンドは定義していない。実際のビルドコマンドは各 `examples/**/backend/canister.yaml` の `build.steps[].commands` にある。そのため本タスクで変更する設定ファイルは `icp.yaml` ではなく `canister.yaml` とする。

対象は `nicp` と Nim バックエンドを持つ examples である。Motoko の `dfx_hello`、`http_outcall/motoko`、`type_test/motoko`、および `icp.yaml` を持たない旧 `dfx` 形式の examples は対象外とする。`src/cli/ndfx_functions/wasm_build.nim` にも同じ旧コマンドがあるが、本ブランチの要求は `nicp` のため変更対象外とする。

### 現状の問題

`src/cli/nicp_functions/wasm_build.nim` は開発・本番の双方で、`wasi2ic` の直後に次を実行している。

```text
ic-wasm main.wasm -o main_ic_wasm.wasm optimize O3
```

`ic-wasm` 0.11.0 以降ではこのサブコマンドが存在しないため、`nicp developmentBuild` と `nicp productionBuild` の両方が失敗する。本番ではその後に `ic-wasm shrink`、最後に Candid メタデータの埋込みを実行する。後者二つは現在も `ic-wasm` が提供しているため維持する。

### 実装方針

1. `compileWasm(release: bool)` の共通パイプラインから、無条件の `ic-wasm ... optimize O3` 呼出しを削除する。
2. `release == true` の場合のみ、`wasm-opt` の実行可能ファイルを `PATH` から解決する。解決できなければ、Nim コンパイル・中間ファイル削除・出力ファイル更新を始める前に、非 0 で終了する。エラーには少なくとも `wasm-opt`、Binaryen、`PATH` を含める。
3. 解決できた場合は `wasi2ic` が生成した `main.wasm` に対し、`wasm-opt -O3 main.wasm -o main_ic_wasm.wasm` を実行し、成功後に一時ファイルを `main.wasm` と置き換える。
4. 続けて既存どおり `ic-wasm shrink`(本番のみ)と `ic-wasm metadata candid:service ...` を実行する。一時出力を経由し、最適化コマンドが失敗した場合は既存の `main.wasm` を置換しない。
5. 開発ビルドは `wasm-opt` を実行しない。これによりローカル反復開発に Binaryen を必須化せず、廃止済み `ic-wasm optimize` による失敗も解消する。

本番の処理順は次のとおりとする。

```text
nim c -d:release
→ wasi2ic
→ wasm-opt -O3
→ ic-wasm shrink
→ ic-wasm metadata candid:service
→ ICP_WASM_OUTPUT_PATH へコピー(指定時)
```

### `wasm-opt` のエラー設計

- 検出対象は `PATH` 上の `wasm-opt` のみとし、固定パスや自動インストールは行わない。
- `productionBuild`/`build` でのみ検証する。`developmentBuild`/`dev` は最適化を行わないため、未導入でも成功できる。
- 未検出時は標準エラーへ、例えば `Error: wasm-opt was not found on PATH. Install Binaryen and make wasm-opt available on PATH.` を出力し、終了コード 1 を返す。
- 見つかってもコマンド実行が失敗した場合は、現在の外部コマンド失敗と同じ扱いで、標準出力・標準エラーの内容を表示して当該終了コードを返す。

### canister 設定の設計

`nicp new` の `backendCanisterYaml` を唯一の書式上の正本とし、Nim の既存 examples にも同じシェルコマンドを設定する。

```yaml
commands:
- bash -c 'if [ "${DFX_NETWORK:-local}" = "local" ]; then nicp developmentBuild; else nicp productionBuild; fi'
```

- `DFX_NETWORK` が未設定または `local` なら `nicp developmentBuild`。
- それ以外(例: `ic`)なら `nicp productionBuild`。従ってデプロイ向けビルドでは `wasm-opt -O3` が必ず適用される。
- 変更対象は `examples/arg_msg_reply`、`counter`、`ecdsa_args`、`http_outcall/nim`、`stable_memory`、`type_test/nim`、`vetkey` の各 `backend/canister.yaml`。既に条件分岐を持つ `http_outcall/nim` も、テンプレートと完全に同一の引用・`bash -c` 形式へ正規化する。

### 環境・ドキュメント設計

- `docker/app/develop.Dockerfile` では、最終 `app` ステージに Binaryen を導入し、既存のツール確認群に `wasm-opt --version` を加える。実行時に必要なのは最終ステージであるため、`wasi-tools` ステージへの導入だけでは不十分である。
- 利用者向けのセットアップ文書には、production build の前提として Binaryen/`wasm-opt` を追加する。OS 固有の導入コマンドを記載する場合は、公式 Binaryen 配布元を参照する。

### 変更ファイル一覧(実装時)

| 区分 | ファイル | 変更内容 |
| --- | --- | --- |
| ビルド本体 | `src/cli/nicp_functions/wasm_build.nim` | `ic-wasm optimize` の削除、本番限定の検出と `wasm-opt -O3` 実行 |
| 生成テンプレート | `src/cli/nicp_functions/new_impl.nim` | ネットワーク別に `developmentBuild`/`productionBuild` を選ぶ `canister.yaml` を維持・正規化 |
| examples | 上記 7 個の `backend/canister.yaml` | テンプレートと同一の条件付きコマンドへ統一 |
| 開発環境 | `docker/app/develop.Dockerfile` | Binaryen 導入と `wasm-opt` の確認 |
| 利用者文書 | 実装時に確認して決定 | 本番ビルドの Binaryen 前提を追記 |

### 検証計画

1. 静的確認: `src/cli/nicp_functions` と対象 examples から `ic-wasm ... optimize` が消え、`ic-wasm shrink` と Candid メタデータ埋込みが残っていることを確認する。
2. 未導入確認: `PATH` から `wasm-opt` を除外した環境で `nicp productionBuild` を実行し、Nim コンパイル前に指定エラー・非 0 終了となることを確認する。同じ環境で `nicp developmentBuild` が `wasm-opt` 検出を要求しないことも確認する。
3. 本番成功確認: `wasm-opt` を含む環境で `nicp productionBuild` を実行し、`wasm-opt -O3`、`ic-wasm shrink`、Candid メタデータ付与の順で成功することと、最終 WASM が出力されることを確認する。
4. 例の統合確認: 各 Nim example について、`DFX_NETWORK=local` では開発ビルド、`DFX_NETWORK=ic` では本番ビルドが選ばれることをコマンド出力またはビルド結果で確認する。
5. コンテナ確認: 開発コンテナのイメージビルドで `wasm-opt --version` が成功することを確認する。

### 非目標

- `ndfx` のビルド実装、Motoko の recipe、旧 `dfx.json`/`build.sh` 形式の examples は変更しない。
- Binaryen の自動ダウンロード、バージョン固定、最適化レベルの利用者指定は扱わない。
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ Another motivational essay:
- [WASI SDK (includes Clang)](https://github.com/WebAssembly/wasi-sdk)
- [ic-wasi-polyfill](https://github.com/wasm-forge/ic-wasi-polyfill)
- [wasi2ic](https://github.com/wasm-forge/wasi2ic)
- [Binaryen (`wasm-opt`)](https://github.com/WebAssembly/binaryen) (required for production builds)
- [Internet Computer SDK](https://internetcomputer.org/docs/current/developer-docs/setup/install/sdk-install)

### Optional
Expand All @@ -45,7 +46,8 @@ apt install -y \
xz-utils \
wget \
curl \
git
git \
binaryen
```

### Install Rust
Expand Down
8 changes: 6 additions & 2 deletions docker/app/develop.Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
# rust
WORKDIR /root
RUN curl https://sh.rustup.rs -sSf | sh -s -- -y
ENV PATH $PATH:/root/.cargo/bin

Check warning on line 16 in docker/app/develop.Dockerfile

View workflow job for this annotation

GitHub Actions / test

Legacy key/value format with whitespace separator should not be used

LegacyKeyValueFormat: "ENV key=value" should be used instead of legacy "ENV key value" format More info: https://docs.docker.com/go/dockerfile/rule/legacy-key-value-format/

# build ic-wasi-polyfill
WORKDIR /root
Expand All @@ -36,6 +36,8 @@
# prevent timezone dialogue
ENV DEBIAN_FRONTEND=noninteractive

# Binaryen provides wasm-opt for production WASM optimization.
# https://github.com/WebAssembly/binaryen
RUN apt update && \
apt upgrade -y && \
apt install -y \
Expand All @@ -49,7 +51,8 @@
wget \
curl \
git \
jq
jq \
binaryen

# LLVM
# reference: https://github.com/ICPorts-labs/chico/blob/main/examples/HelloWorld/Dockerfile#L32
Expand Down Expand Up @@ -83,9 +86,9 @@
RUN tar -xzf wasi-sdk.tar.gz
RUN rm wasi-sdk.tar.gz
RUN mv "wasi-sdk-${WASI_VERSION_FULL}-x86_64-linux" ".wasi-sdk"
ENV WASI_SDK_PATH "/root/.wasi-sdk"

Check warning on line 89 in docker/app/develop.Dockerfile

View workflow job for this annotation

GitHub Actions / test

Legacy key/value format with whitespace separator should not be used

LegacyKeyValueFormat: "ENV key=value" should be used instead of legacy "ENV key value" format More info: https://docs.docker.com/go/dockerfile/rule/legacy-key-value-format/
RUN echo $WASI_SDK_PATH
ENV PATH $PATH:"${WASI_SDK_PATH}/bin"

Check warning on line 91 in docker/app/develop.Dockerfile

View workflow job for this annotation

GitHub Actions / test

Legacy key/value format with whitespace separator should not be used

LegacyKeyValueFormat: "ENV key=value" should be used instead of legacy "ENV key value" format More info: https://docs.docker.com/go/dockerfile/rule/legacy-key-value-format/

# webt
# https://github.com/WebAssembly/wabt
Expand All @@ -96,7 +99,7 @@
RUN curl https://nim-lang.org/choosenim/init.sh -o init.sh
RUN sh init.sh -y
RUN rm -f init.sh
ENV PATH $PATH:/root/.nimble/bin

Check warning on line 102 in docker/app/develop.Dockerfile

View workflow job for this annotation

GitHub Actions / test

Legacy key/value format with whitespace separator should not be used

LegacyKeyValueFormat: "ENV key=value" should be used instead of legacy "ENV key value" format More info: https://docs.docker.com/go/dockerfile/rule/legacy-key-value-format/

# nimlangserver
# https://github.com/nim-lang/langserver/releases/latest
Expand All @@ -115,11 +118,11 @@
RUN tar -xvf node-v${NODE_VERSION}-linux-x64.tar.xz
RUN rm node-v${NODE_VERSION}-linux-x64.tar.xz
RUN mv node-v${NODE_VERSION}-linux-x64 .node
ENV PATH $PATH:/root/.node/bin

Check warning on line 121 in docker/app/develop.Dockerfile

View workflow job for this annotation

GitHub Actions / test

Legacy key/value format with whitespace separator should not be used

LegacyKeyValueFormat: "ENV key=value" should be used instead of legacy "ENV key value" format More info: https://docs.docker.com/go/dockerfile/rule/legacy-key-value-format/

# pnpm
RUN curl -fsSL https://get.pnpm.io/install.sh | bash -s -- -y
ENV PATH $PATH:/root/.local/share/pnpm
ENV PATH $PATH:/root/.local/share/pnpm/bin

Check warning on line 125 in docker/app/develop.Dockerfile

View workflow job for this annotation

GitHub Actions / test

Legacy key/value format with whitespace separator should not be used

LegacyKeyValueFormat: "ENV key=value" should be used instead of legacy "ENV key value" format More info: https://docs.docker.com/go/dockerfile/rule/legacy-key-value-format/

# ic-mops, compile motoko
# https://github.com/dfinity/ic-mops
Expand All @@ -133,9 +136,9 @@
# copy from wasi-tools
WORKDIR /root
COPY --from=wasi-tools /root/ic-wasi-polyfill/target/wasm32-wasip1/release/* /root/.ic-wasi-polyfill/
ENV IC_WASI_POLYFILL_PATH "/root/.ic-wasi-polyfill"

Check warning on line 139 in docker/app/develop.Dockerfile

View workflow job for this annotation

GitHub Actions / test

Legacy key/value format with whitespace separator should not be used

LegacyKeyValueFormat: "ENV key=value" should be used instead of legacy "ENV key value" format More info: https://docs.docker.com/go/dockerfile/rule/legacy-key-value-format/
COPY --from=wasi-tools /root/.cargo/bin/* /root/.cargo/bin/
ENV PATH $PATH:/root/.cargo/bin

Check warning on line 141 in docker/app/develop.Dockerfile

View workflow job for this annotation

GitHub Actions / test

Legacy key/value format with whitespace separator should not be used

LegacyKeyValueFormat: "ENV key=value" should be used instead of legacy "ENV key value" format More info: https://docs.docker.com/go/dockerfile/rule/legacy-key-value-format/

# check command installed successfully
RUN nim -v
Expand All @@ -145,6 +148,7 @@
RUN forge --version
RUN ic-wasm --version
RUN wasi2ic --version
RUN wasm-opt --version


RUN git config --global --add safe.directory /application
Expand Down
2 changes: 1 addition & 1 deletion examples/arg_msg_reply/backend/canister.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,4 @@ build:
steps:
- type: script
commands:
- nicp developmentBuild
- bash -c 'if [ "${DFX_NETWORK:-local}" = "local" ]; then nicp developmentBuild; else nicp productionBuild; fi'
2 changes: 1 addition & 1 deletion examples/counter/backend/canister.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,4 @@ build:
steps:
- type: script
commands:
- nicp developmentBuild
- bash -c 'if [ "${DFX_NETWORK:-local}" = "local" ]; then nicp developmentBuild; else nicp productionBuild; fi'
12 changes: 12 additions & 0 deletions examples/counter/icp.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,15 @@

canisters:
- backend

networks:
- name: local
mode: managed
gateway:
bind: "0.0.0.0"
port: 8000
ii: true # iiが有効になる。 http://id.ai.localhost:8000/#authorize

environments:
- name: local
network: local
12 changes: 12 additions & 0 deletions examples/dfx_hello/icp.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,15 @@

canisters:
- backend

networks:
- name: local
mode: managed
gateway:
bind: "0.0.0.0"
port: 8000
ii: true # iiが有効になる。 http://id.ai.localhost:8000/#authorize

environments:
- name: local
network: local
2 changes: 1 addition & 1 deletion examples/ecdsa_args/backend/canister.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,4 @@ build:
steps:
- type: script
commands:
- nicp developmentBuild
- bash -c 'if [ "${DFX_NETWORK:-local}" = "local" ]; then nicp developmentBuild; else nicp productionBuild; fi'
12 changes: 12 additions & 0 deletions examples/http_outcall/motoko/icp.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,15 @@

canisters:
- backend

networks:
- name: local
mode: managed
gateway:
bind: "0.0.0.0"
port: 8000
ii: true # iiが有効になる。 http://id.ai.localhost:8000/#authorize

environments:
- name: local
network: local
2 changes: 1 addition & 1 deletion examples/http_outcall/nim/backend/canister.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,4 @@ build:
steps:
- type: script
commands:
- if [ \"${DFX_NETWORK:-local}\" = \"local\" ]; then nicp developmentBuild; else nicp productionBuild; fi
- bash -c 'if [ "${DFX_NETWORK:-local}" = "local" ]; then nicp developmentBuild; else nicp productionBuild; fi'
5 changes: 5 additions & 0 deletions examples/http_outcall/nim/icp.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,8 @@ networks:
gateway:
bind: "0.0.0.0"
port: 8000
ii: true # iiが有効になる。 http://id.ai.localhost:8000/#authorize

environments:
- name: local
network: local
2 changes: 1 addition & 1 deletion examples/stable_memory/backend/canister.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,4 @@ build:
steps:
- type: script
commands:
- nicp developmentBuild
- bash -c 'if [ "${DFX_NETWORK:-local}" = "local" ]; then nicp developmentBuild; else nicp productionBuild; fi'
12 changes: 12 additions & 0 deletions examples/stable_memory/icp.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,15 @@

canisters:
- backend

networks:
- name: local
mode: managed
gateway:
bind: "0.0.0.0"
port: 8000
ii: true # iiが有効になる。 http://id.ai.localhost:8000/#authorize

environments:
- name: local
network: local
12 changes: 12 additions & 0 deletions examples/type_test/motoko/icp.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,15 @@

canisters:
- backend

networks:
- name: local
mode: managed
gateway:
bind: "0.0.0.0"
port: 8000
ii: true # iiが有効になる。 http://id.ai.localhost:8000/#authorize

environments:
- name: local
network: local
2 changes: 1 addition & 1 deletion examples/type_test/nim/backend/canister.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,4 @@ build:
steps:
- type: script
commands:
- nicp developmentBuild
- bash -c 'if [ "${DFX_NETWORK:-local}" = "local" ]; then nicp developmentBuild; else nicp productionBuild; fi'
12 changes: 12 additions & 0 deletions examples/type_test/nim/icp.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,15 @@

canisters:
- backend

networks:
- name: local
mode: managed
gateway:
bind: "0.0.0.0"
port: 8000
ii: true # iiが有効になる。 http://id.ai.localhost:8000/#authorize

environments:
- name: local
network: local
2 changes: 1 addition & 1 deletion examples/vetkey/backend/canister.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,4 @@ build:
steps:
- type: script
commands:
- nicp developmentBuild
- bash -c 'if [ "${DFX_NETWORK:-local}" = "local" ]; then nicp developmentBuild; else nicp productionBuild; fi'
4 changes: 4 additions & 0 deletions examples/vetkey/icp.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,7 @@ networks:
bind: "0.0.0.0"
port: 8000
ii: true # iiが有効になる。 http://id.ai.localhost:8000/#authorize

environments:
- name: local
network: local
26 changes: 25 additions & 1 deletion src/cli/nicp_functions/new_impl.nim
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ build:
steps:
- type: script
commands:
- bash -c 'if [ \"${DFX_NETWORK:-local}\" = \"local\" ]; then nicp developmentBuild; else nicp productionBuild; fi'
- bash -c 'if [ "${DFX_NETWORK:-local}" = "local" ]; then nicp developmentBuild; else nicp productionBuild; fi'
"""

const backendGitignore = """
Expand Down Expand Up @@ -216,13 +216,37 @@ proc renderIcpYaml(hasFrontend: bool): string =
canisters:
- backend
- frontend

networks:
- name: local
mode: managed
gateway:
bind: "0.0.0.0"
port: 8000
ii: true # iiが有効になる。 http://id.ai.localhost:8000/#authorize

environments:
- name: local
network: local
"""
else:
result = """
# yaml-language-server: $schema=https://github.com/dfinity/icp-cli/raw/refs/tags/v0.1.0/docs/schemas/icp-yaml-schema.json

canisters:
- backend

networks:
- name: local
mode: managed
gateway:
bind: "0.0.0.0"
port: 8000
ii: true # iiが有効になる。 http://id.ai.localhost:8000/#authorize

environments:
- name: local
network: local
"""

proc renderBackendReadme(): string =
Expand Down
Loading
Loading