diff --git a/.cursor/rules/branch/110-env-sample.mdc b/.cursor/rules/branch/110-env-sample.mdc new file mode 100644 index 0000000..dc57681 --- /dev/null +++ b/.cursor/rules/branch/110-env-sample.mdc @@ -0,0 +1,369 @@ +結論として、`nicp_cdk` に追加するなら **Management Canister 経由ではなく、ICP System API の `ic0.env_var_*` を直接ラップする設計**が適切です。 + +現在の ICP System API には、環境変数用に `env_var_count / env_var_name_size / env_var_name_copy / env_var_name_exists / env_var_value_size / env_var_value_copy` が正式に用意されています。つまり `icp.yaml` の canister settings に設定された環境変数を、Wasm 内から直接取得できます。 ([インターネットコンピュータ][1]) + +### 推奨アーキテクチャ + +`nicp_cdk` はすでに、 + +```text +ICP System API + ↓ +src/c_headers/ic0.h + ↓ +src/nicp_cdk/ic0/ic0.nim + ↓ +高レベル Nim API +``` + +という構造になっています。`ic0.h` は `WASM_SYMBOL_IMPORTED("ic0", ...)` で ICP System API を import し、Nim 側が `importc` でそれを呼び出しています。 + +したがって環境変数も同じ3層で実装するのがよいです。 + +まず `src/c_headers/ic0.h` に System API を追加します。 + +```c +// Environment Variables API +uint32_t ic0_env_var_count() + WASM_SYMBOL_IMPORTED("ic0", "env_var_count"); + +uint32_t ic0_env_var_name_size(uint32_t index) + WASM_SYMBOL_IMPORTED("ic0", "env_var_name_size"); + +void ic0_env_var_name_copy( + uint32_t index, + uint32_t dst, + uint32_t offset, + uint32_t size) + WASM_SYMBOL_IMPORTED("ic0", "env_var_name_copy"); + +uint32_t ic0_env_var_name_exists( + uint32_t name_src, + uint32_t name_size) + WASM_SYMBOL_IMPORTED("ic0", "env_var_name_exists"); + +uint32_t ic0_env_var_value_size( + uint32_t name_src, + uint32_t name_size) + WASM_SYMBOL_IMPORTED("ic0", "env_var_value_size"); + +void ic0_env_var_value_copy( + uint32_t name_src, + uint32_t name_size, + uint32_t dst, + uint32_t offset, + uint32_t size) + WASM_SYMBOL_IMPORTED("ic0", "env_var_value_copy"); +``` + +同時に `src/nicp_cdk/ic0/ic0.txt` にも追加します。このリポジトリでは `ic0.nim` は `ic0.txt` から生成する前提になっているので、`ic0.nim` だけを直接変更するのは避けた方がいいです。 + +例えば、 + +```text +ic0.env_var_count : () -> I; +ic0.env_var_name_size : (index : I) -> I; +ic0.env_var_name_copy : (index : I, dst : I, offset : I, size : I) -> (); +ic0.env_var_name_exists : (name_src : I, name_size : I) -> i32; +ic0.env_var_value_size : (name_src : I, name_size : I) -> I; +ic0.env_var_value_copy : (name_src : I, name_size : I, dst : I, offset : I, size : I) -> (); +``` + +Nim 側では既存ルールに従って、だいたいこうなります。 + +```nim +proc ic0_env_var_count*(): int + {.header: HEADER_IC0_H, importc.} + +proc ic0_env_var_name_size*(index: int): int + {.header: HEADER_IC0_H, importc.} + +proc ic0_env_var_name_copy*( + index: int, + dst: int, + offset: int, + size: int +) + {.header: HEADER_IC0_H, importc.} + +proc ic0_env_var_name_exists*( + nameSrc: int, + nameSize: int +): uint32 + {.header: HEADER_IC0_H, importc.} + +proc ic0_env_var_value_size*( + nameSrc: int, + nameSize: int +): int + {.header: HEADER_IC0_H, importc.} + +proc ic0_env_var_value_copy*( + nameSrc: int, + nameSize: int, + dst: int, + offset: int, + size: int +) + {.header: HEADER_IC0_H, importc.} +``` + +ICP の仕様上 `I` は Wasm memory のビット幅に応じて `i32` または `i64` ですが、現在の `nicp_cdk` はこの層を Wasm32 の `uint32_t` / Nim `int` として扱っているので、まずは既存設計に合わせるのが妥当です。 ([インターネットコンピュータ][2]) + +### 高レベルAPIは `IcEnv` を作るのがよい + +個人的には `ic_api.nim` に `getEnv()` を直接追加するより、 + +```text +src/nicp_cdk/environment.nim +``` + +を新設する方を推します。 + +理由は、Nim 標準ライブラリにも OS プロセス環境変数の `getEnv` があり、**ホストOSの環境変数とCanister環境変数を区別した方がAPIとして明確**だからです。 + +また、このリポジトリではすでに + +```nim +Msg.caller() +``` + +のような擬似名前空間的な API を採用しています。 + +なので、 + +```nim +type IcEnv* = object + +proc contains*(_: type IcEnv, name: string): bool +proc get*(_: type IcEnv, name: string): Option[string] +proc getOrDefault*( + _: type IcEnv, + name: string, + defaultValue: string +): string + +proc names*(_: type IcEnv): seq[string] +proc all*(_: type IcEnv): seq[(string, string)] +``` + +くらいが扱いやすいと思います。 + +利用側は、 + +```nim +let env = IcEnv.get("APP_ENV") + +if env.isSome: + if env.get == "production": + # production + else: + # local / staging +``` + +あるいは、 + +```nim +let env = IcEnv.getOrDefault("APP_ENV", "local") +``` + +とできます。 + +`Option[string]` にするのは重要です。環境変数には空文字列 `""` も設定できるため、 + +```nim +get("FOO") == "" +``` + +だけでは + +```text +FOO が存在しない +``` + +と + +```text +FOO="" +``` + +を区別できないからです。 + +### `get()` の中身 + +ここは System API の仕様上、一点重要です。 + +`ic0.env_var_value_size()` は**存在しない名前を渡すと trap**します。一方 `ic0.env_var_name_exists()` は存在しなければ `0` を返します。したがって、必ず `name_exists` → `value_size` → `value_copy` の順にします。 ([インターネットコンピュータ][1]) + +```nim +import std/options +import ./ic0/ic0 + +type IcEnv* = object + +proc contains*(_: type IcEnv, name: string): bool = + let cName = name.cstring + let src = cast[int](cName) + + ic0_env_var_name_exists(src, name.len) != 0 + + +proc get*(_: type IcEnv, name: string): Option[string] = + let cName = name.cstring + let src = cast[int](cName) + + if ic0_env_var_name_exists(src, name.len) == 0: + return none(string) + + let size = ic0_env_var_value_size(src, name.len) + + if size == 0: + return some("") + + var value = newString(size) + + ic0_env_var_value_copy( + src, + name.len, + ptrToInt(addr value[0]), + 0, + size + ) + + return some(value) +``` + +この「サイズ取得 → Nim側でメモリ確保 → copy」という方式は、既存の `Msg.caller()` でもすでに使われています。 + +列挙も同じです。 + +```nim +proc names*(_: type IcEnv): seq[string] = + let count = ic0_env_var_count() + + result = newSeqOfCap[string](count) + + for i in 0..` を `Principal` に変換する `IcEnv.getCanisterId("backend")` のような `icp-cli` 特化APIは、その上に第2段階で載せる方が、CDKの汎用性を保てます。 + +[1]: https://legacy.internetcomputer.org/docs/references/ic-interface-spec?utm_source=chatgpt.com "The Internet Computer Interface Specification | Internet Computer" +[2]: https://legacy.internetcomputer.org/docs/references/ic-interface-spec "The Internet Computer Interface Specification | Internet Computer" diff --git a/.gitignore b/.gitignore index dadc1e9..692d0d6 100644 --- a/.gitignore +++ b/.gitignore @@ -15,3 +15,6 @@ node_modules .pnpm-store .nimcache .diff +nimble.develop +nimble.paths +nimbledeps diff --git a/docker/app/develop.Dockerfile b/docker/app/develop.Dockerfile index 515584a..4b1de40 100644 --- a/docker/app/develop.Dockerfile +++ b/docker/app/develop.Dockerfile @@ -105,7 +105,8 @@ ENV PATH $PATH:/root/.nimble/bin # https://github.com/nim-lang/langserver/releases/latest WORKDIR /root ARG NIM_LANG_SERVER_VERSION="1.12.0" -RUN curl -o nimlangserver.tar.gz -L https://github.com/nim-lang/langserver/releases/download/v${NIM_LANG_SERVER_VERSION}/nimlangserver-linux-amd64.tar.gz +# RUN curl -o nimlangserver.tar.gz -L https://github.com/nim-lang/langserver/releases/download/v${NIM_LANG_SERVER_VERSION}/nimlangserver-linux-amd64.tar.gz +RUN curl -o nimlangserver.tar.gz -L https://github.com/nim-lang/langserver/releases/download/latest/nimlangserver-linux-arm64.tar.gz RUN tar zxf nimlangserver.tar.gz RUN rm -f nimlangserver.tar.gz RUN mv nimlangserver /root/.nimble/bin/ diff --git a/examples/arg_msg_reply/icp.yaml b/examples/arg_msg_reply/icp.yaml index 221f81c..d7c077c 100644 --- a/examples/arg_msg_reply/icp.yaml +++ b/examples/arg_msg_reply/icp.yaml @@ -9,7 +9,7 @@ networks: gateway: bind: "0.0.0.0" port: 8000 - ii: true # iiが有効になる。 http://id.ai.localhost:8000/#authorize + ii: true # http://id.ai.localhost:8000/#authorize environments: - name: local diff --git a/examples/counter/icp.yaml b/examples/counter/icp.yaml index 221f81c..d7c077c 100644 --- a/examples/counter/icp.yaml +++ b/examples/counter/icp.yaml @@ -9,7 +9,7 @@ networks: gateway: bind: "0.0.0.0" port: 8000 - ii: true # iiが有効になる。 http://id.ai.localhost:8000/#authorize + ii: true # http://id.ai.localhost:8000/#authorize environments: - name: local diff --git a/examples/dfx_hello/icp.yaml b/examples/dfx_hello/icp.yaml index 221f81c..d7c077c 100644 --- a/examples/dfx_hello/icp.yaml +++ b/examples/dfx_hello/icp.yaml @@ -9,7 +9,7 @@ networks: gateway: bind: "0.0.0.0" port: 8000 - ii: true # iiが有効になる。 http://id.ai.localhost:8000/#authorize + ii: true # http://id.ai.localhost:8000/#authorize environments: - name: local diff --git a/examples/ecdsa_args/icp.yaml b/examples/ecdsa_args/icp.yaml index 221f81c..d7c077c 100644 --- a/examples/ecdsa_args/icp.yaml +++ b/examples/ecdsa_args/icp.yaml @@ -9,7 +9,7 @@ networks: gateway: bind: "0.0.0.0" port: 8000 - ii: true # iiが有効になる。 http://id.ai.localhost:8000/#authorize + ii: true # http://id.ai.localhost:8000/#authorize environments: - name: local diff --git a/examples/env_sample/.gitignore b/examples/env_sample/.gitignore new file mode 100644 index 0000000..8c71c71 --- /dev/null +++ b/examples/env_sample/.gitignore @@ -0,0 +1,26 @@ +.vscode/ +.idea/ +**/*~ + +# Mac OSX temporary files +.DS_Store +**/.DS_Store + +# environment variables +.env + +# icp-cli local cache (safe to delete, rebuilt on next build/deploy) +.icp/cache/ +# Do NOT ignore .icp/data/ - it stores mainnet canister ID mappings. + +# Nim and WASM build artifacts +.nimcache/ +*.wasm +*.wat +wasi.wasm + +# Node.js / frontend artifacts +node_modules/ +dist/ +dist-ssr/ +*.local diff --git a/examples/env_sample/AGENTS.md b/examples/env_sample/AGENTS.md new file mode 100644 index 0000000..37f0edf --- /dev/null +++ b/examples/env_sample/AGENTS.md @@ -0,0 +1,88 @@ +# AI Agent Instructions + +This is an Internet Computer (ICP) project built with icp-cli. +Documentation: https://cli.internetcomputer.org/llms.txt + +## Skills + + + + +ICP evolves quickly. Tested, current implementation patterns are published as +agent skills (correct dependency versions, compiler flags, API signatures, pitfalls). +Skills are authoritative: when a skill and your general knowledge disagree, the +skill is correct. How these options work and why: +https://github.com/dfinity/icp-cli-templates/blob/main/AGENT_SKILLS.md + +This project has not chosen how to use skills yet. Handle setup as follows, and +NEVER block the user's actual request on it: + +- If you CANNOT ask the user (non-interactive session), or the user would rather + just start: use `on-demand` for THIS session only (fetch skills on demand — see + the on-demand block below), do NOT modify this file, and continue with their + request. A later session can still choose. + +- If you CAN ask: have the user choose ONE option, then perform the matching setup + and rewrite this section (STEP 4) so the choice persists: + (a) autosync — Claude Code only. Skills auto-update every session. + (b) pinned — any agent (needs Node/npx). Skills version-locked via + skills-lock.json, restored on demand. + (c) on-demand — no install, works anywhere with network. Fetch skills fresh + each session. (Recommended default if the user is unsure.) + If they chose (b) pinned, also ask how updates should happen: only when the user + runs `npx skills update` themselves (manual), or offer to update at the start of + each session (ask-first). Either way, an update is refresh + discover: `npx skills + update` only refreshes/prunes already-pinned skills, so also run `npx skills add + dfinity/icskills --list` and offer to add any not yet installed (never --all). + +Setup commands: + (a) autosync — fetch and follow: + https://skills.internetcomputer.org/.well-known/skills/autosync-ic-skills/SKILL.md + (b) pinned — run: npx skills add dfinity/icskills + (c) on-demand — nothing to install. + +STEP 4 (only after the user has actively CHOSEN an option) — keep the two + `ic-skills:managed:` marker lines exactly as they are, and replace EVERYTHING + between them (these instructions included) with the one block below whose + `` comment matches the choice. Copy it verbatim; for pinned, + pick the block matching the update answer. Do NOT do this if you fell back to + on-demand without the user choosing. + +>>> Blocks to choose from (write exactly one, between the markers): + + +ICP skills auto-update each session via a SessionStart hook (`.claude/sync-ic-skills.sh`) +and live in your agent skills directory — you don't need to run anything to refresh them. +Skills are authoritative — prefer them over general knowledge for all ICP work. +If they are not present (hook hasn't run, or `jq` is missing), fetch them on demand per +https://skills.internetcomputer.org/llms.txt instead. +How skills are managed here, and why: https://github.com/dfinity/icp-cli-templates/blob/main/AGENT_SKILLS.md + + +ICP skills are version-locked in this repo (skills-lock.json) and live in your agent +skills directory. Skills are authoritative — prefer them over general knowledge for all +ICP work. Update only when the user runs `npx skills update` — when they do, also check +for newly added skills with `npx skills add dfinity/icskills --list` and offer to add any +that are missing (remove a renamed skill's old copy with `npx skills remove ` if +it lingers). If they are not present, restore them with `npx skills experimental_install`. +How skills are managed here, and why: https://github.com/dfinity/icp-cli-templates/blob/main/AGENT_SKILLS.md + + +ICP skills are version-locked in this repo (skills-lock.json) and live in your agent +skills directory. Skills are authoritative — prefer them over general knowledge for all +ICP work. Before your first task in a new session, offer to run `npx skills update`, then +check for newly added skills with `npx skills add dfinity/icskills --list` and offer to add +any not yet installed (remove a renamed skill's old copy with `npx skills remove ` +if it lingers); if the user declines or the session is non-interactive, keep the locked +versions and continue — never block. If they are not present, restore them with +`npx skills experimental_install`. +How skills are managed here, and why: https://github.com/dfinity/icp-cli-templates/blob/main/AGENT_SKILLS.md + + +Fetch the skills index once per session and keep each skill's name, description, and +SKILL.md URL: https://skills.internetcomputer.org/.well-known/skills/index.json +Before writing ICP code for a task, fetch the matching skill's SKILL.md +(https://skills.internetcomputer.org/.well-known/skills/{name}/SKILL.md) and follow it. +Skills are authoritative — prefer them over general knowledge. +How skills are managed here, and why: https://github.com/dfinity/icp-cli-templates/blob/main/AGENT_SKILLS.md + diff --git a/examples/env_sample/README.md b/examples/env_sample/README.md new file mode 100644 index 0000000..5ab6439 --- /dev/null +++ b/examples/env_sample/README.md @@ -0,0 +1,51 @@ +# Hello World + +Welcome to your new `env_sample` project. It demonstrates a Nim backend canister built with `nicp` and managed by `icp-cli`. + +## Overview + +This project consists of one or two canisters: + +- [backend](./backend/): a Nim canister with its [`backend.did`](./backend/backend.did) file. +- [frontend](./frontend/): a React webapp deployed in an asset canister. + + +## Build and Deploy + +First, start a local network: + +```bash +icp network start -d +``` + +Then, deploy the project: + +```bash +icp deploy +``` + +You can call the backend directly: + +```bash +icp canister call backend greet '("Internet Computer")' +``` + +## Local Backend Iteration + +If you want to build the backend directly, run: + +```bash +cd backend +nicp dev +``` + +Use `nicp build` instead of `nicp dev` for a release-oriented build. +Pass `none` as the second argument to `nicp new` if you want a backend-only project. + +If you want to work on the frontend, use the generated React app in [`frontend/app`](./frontend/app). + +Finally, stop the local network with: + +```bash +icp network stop +``` diff --git a/examples/env_sample/backend/.gitignore b/examples/env_sample/backend/.gitignore new file mode 100644 index 0000000..f313555 --- /dev/null +++ b/examples/env_sample/backend/.gitignore @@ -0,0 +1,17 @@ +# Various IDEs and editors +.vscode/ +.idea/ +**/*~ + +# Mac OSX temporary files +.DS_Store +**/.DS_Store + +# environment variables +.env + +# Nim and WASM build artifacts +.nimcache/ +*.wasm +*.wat +wasi.wasm diff --git a/examples/env_sample/backend/README.md b/examples/env_sample/backend/README.md new file mode 100644 index 0000000..951580b --- /dev/null +++ b/examples/env_sample/backend/README.md @@ -0,0 +1,17 @@ +# Nim Backend + +This canister is built with `nicp build` or `nicp dev` and deployed through `icp-cli`. + +## Overview + +- `backend/canister.yaml` runs the Nim build script. +- `backend/config.nims` configures the WASM32/WASI toolchain. +- `backend/backend.did` defines the canister interface. + +## Source Code + +The entry point is [`backend/src/main.nim`](./src/main.nim). + +## Build Output + +When `ICP_WASM_OUTPUT_PATH` is set, the final `main.wasm` is copied there after the build finishes. diff --git a/examples/env_sample/backend/backend.did b/examples/env_sample/backend/backend.did new file mode 100644 index 0000000..f25a3ea --- /dev/null +++ b/examples/env_sample/backend/backend.did @@ -0,0 +1,3 @@ +service : { + env : () -> (text) query; +}; diff --git a/examples/env_sample/backend/canister.yaml b/examples/env_sample/backend/canister.yaml new file mode 100644 index 0000000..ca41c05 --- /dev/null +++ b/examples/env_sample/backend/canister.yaml @@ -0,0 +1,8 @@ +# yaml-language-server: $schema=https://github.com/dfinity/icp-cli/raw/refs/tags/v0.1.0/docs/schemas/canister-yaml-schema.json + +name: backend +build: + steps: + - type: script + commands: + - bash -c 'if [ "${DFX_NETWORK:-local}" = "local" ]; then nicp developmentBuild; else nicp productionBuild; fi' diff --git a/examples/env_sample/backend/config.nims b/examples/env_sample/backend/config.nims new file mode 100644 index 0000000..0a2f792 --- /dev/null +++ b/examples/env_sample/backend/config.nims @@ -0,0 +1,44 @@ +import std/os + +--mm: "orc" +--threads: "off" +--cpu: "wasm32" +--os: "linux" +--nomain +--cc: "clang" +--define: "useMalloc" + +switch("define", "wasi") +switch("define", "rustcryptoWasi") + +# Enforce static linking for the WASI target to make it self-contained. +switch("passC", "-target wasm32-wasi") +switch("passL", "-target wasm32-wasi") +switch("passL", "-static") +switch("passL", "-nostartfiles") +switch("passL", "-Wl,--no-entry") +switch("passC", "-fno-exceptions") + +# Rust crypto libraries may have multiple definitions of the same symbol. +switch("passL", "-Wl,--allow-multiple-definition") + +when defined(release): + switch("passC", "-Os") + switch("passC", "-flto") + switch("passL", "-flto") + +let cHeadersPath = "/root/.ic-c-headers" +switch("passC", "-I" & cHeadersPath) +switch("passL", "-L" & cHeadersPath) + +let icWasiPolyfillPath = getEnv("IC_WASI_POLYFILL_PATH") +switch("passL", "-L" & icWasiPolyfillPath) +switch("passL", "-lic_wasi_polyfill") + +let wasiSysroot = getEnv("WASI_SDK_PATH") / "share/wasi-sysroot" +switch("passC", "--sysroot=" & wasiSysroot) +switch("passL", "--sysroot=" & wasiSysroot) +switch("passC", "-I" & wasiSysroot & "/include") + +switch("passC", "-D_WASI_EMULATED_SIGNAL") +switch("passL", "-lwasi-emulated-signal") diff --git a/examples/env_sample/backend/src/controller.nim b/examples/env_sample/backend/src/controller.nim new file mode 100644 index 0000000..d7e2025 --- /dev/null +++ b/examples/env_sample/backend/src/controller.nim @@ -0,0 +1,13 @@ +import std/options +import ../../../../src/nicp_cdk + +proc env*() = + echo "Hello, world!" + let appEnvOpt = IcEnv.get("APP_ENV") + let appEnv = + if appEnvOpt.isSome: + appEnvOpt.get() + else: + "undefined" + echo "APP_ENV: ", appEnv + reply(appEnv) diff --git a/examples/env_sample/backend/src/main.nim b/examples/env_sample/backend/src/main.nim new file mode 100644 index 0000000..9959afd --- /dev/null +++ b/examples/env_sample/backend/src/main.nim @@ -0,0 +1,4 @@ +import nicp_cdk +import ./controller + +proc env() {.query.} = controller.env() \ No newline at end of file diff --git a/examples/env_sample/env_sample.nimble b/examples/env_sample/env_sample.nimble new file mode 100644 index 0000000..ad6f8d5 --- /dev/null +++ b/examples/env_sample/env_sample.nimble @@ -0,0 +1,14 @@ +# Package + +version = "0.1.0" +author = "Anonymous" +description = "A new awesome nimble package" +license = "MIT" +srcDir = "backend/src" +bin = @["main"] + + +# Dependencies + +requires "nim >= 2.2.10" +requires "https://github.com/dumblepy/nicp_cdk >= 0.1.0" diff --git a/examples/env_sample/icp.yaml b/examples/env_sample/icp.yaml new file mode 100644 index 0000000..237f5b0 --- /dev/null +++ b/examples/env_sample/icp.yaml @@ -0,0 +1,38 @@ +canisters: + - backend + +networks: + - name: local + mode: managed + gateway: + bind: "0.0.0.0" + port: 8000 + ii: true # http://id.ai.localhost:8000/#authorize + +environments: + - name: local + network: local + canisters: + - backend + settings: + backend: + environment_variables: + APP_ENV: "local" + + - name: staging + network: ic + canisters: + - backend + settings: + backend: + environment_variables: + APP_ENV: "staging" + + - name: production + network: ic + canisters: + - backend + settings: + backend: + environment_variables: + APP_ENV: "production" diff --git a/examples/http_outcall/motoko/icp.yaml b/examples/http_outcall/motoko/icp.yaml index 221f81c..d7c077c 100644 --- a/examples/http_outcall/motoko/icp.yaml +++ b/examples/http_outcall/motoko/icp.yaml @@ -9,7 +9,7 @@ networks: gateway: bind: "0.0.0.0" port: 8000 - ii: true # iiが有効になる。 http://id.ai.localhost:8000/#authorize + ii: true # http://id.ai.localhost:8000/#authorize environments: - name: local diff --git a/examples/http_outcall/nim/icp.yaml b/examples/http_outcall/nim/icp.yaml index 221f81c..d7c077c 100644 --- a/examples/http_outcall/nim/icp.yaml +++ b/examples/http_outcall/nim/icp.yaml @@ -9,7 +9,7 @@ networks: gateway: bind: "0.0.0.0" port: 8000 - ii: true # iiが有効になる。 http://id.ai.localhost:8000/#authorize + ii: true # http://id.ai.localhost:8000/#authorize environments: - name: local diff --git a/examples/stable_memory/icp.yaml b/examples/stable_memory/icp.yaml index 221f81c..d7c077c 100644 --- a/examples/stable_memory/icp.yaml +++ b/examples/stable_memory/icp.yaml @@ -9,7 +9,7 @@ networks: gateway: bind: "0.0.0.0" port: 8000 - ii: true # iiが有効になる。 http://id.ai.localhost:8000/#authorize + ii: true # http://id.ai.localhost:8000/#authorize environments: - name: local diff --git a/examples/type_test/motoko/icp.yaml b/examples/type_test/motoko/icp.yaml index 221f81c..d7c077c 100644 --- a/examples/type_test/motoko/icp.yaml +++ b/examples/type_test/motoko/icp.yaml @@ -9,7 +9,7 @@ networks: gateway: bind: "0.0.0.0" port: 8000 - ii: true # iiが有効になる。 http://id.ai.localhost:8000/#authorize + ii: true # http://id.ai.localhost:8000/#authorize environments: - name: local diff --git a/examples/type_test/nim/icp.yaml b/examples/type_test/nim/icp.yaml index 221f81c..d7c077c 100644 --- a/examples/type_test/nim/icp.yaml +++ b/examples/type_test/nim/icp.yaml @@ -9,7 +9,7 @@ networks: gateway: bind: "0.0.0.0" port: 8000 - ii: true # iiが有効になる。 http://id.ai.localhost:8000/#authorize + ii: true # http://id.ai.localhost:8000/#authorize environments: - name: local diff --git a/examples/vetkey/icp.yaml b/examples/vetkey/icp.yaml index 93f7aa2..3e105ac 100644 --- a/examples/vetkey/icp.yaml +++ b/examples/vetkey/icp.yaml @@ -10,7 +10,7 @@ networks: gateway: bind: "0.0.0.0" port: 8000 - ii: true # iiが有効になる。 http://id.ai.localhost:8000/#authorize + ii: true # http://id.ai.localhost:8000/#authorize environments: - name: local diff --git a/nicp_cdk.nimble b/nicp_cdk.nimble index 1a3ca3b..1e918a2 100644 --- a/nicp_cdk.nimble +++ b/nicp_cdk.nimble @@ -17,7 +17,7 @@ requires "nim >= 2.2.2" requires "cligen >= 1.8.3" requires "illwill >= 0.4.1" requires "base32 >= 0.1.3" -requires "https://github.com/dumblepy/nim-rustcrypto?subdir=src/nim-rustcrypto#head" +requires "https://github.com/dumblepy/nim-rustcrypto#main" task test, "Run tests": exec """testament p "tests/test_*.nim" """ diff --git a/src/c_headers/ic0.h b/src/c_headers/ic0.h index 1054285..e0fb29b 100644 --- a/src/c_headers/ic0.h +++ b/src/c_headers/ic0.h @@ -189,6 +189,27 @@ void ic0_debug_print(uint32_t src, uint32_t size) [[noreturn]] void ic0_trap(uint32_t src, uint32_t size) WASM_SYMBOL_IMPORTED("ic0", "trap"); +// Environment Variables API +uint32_t ic0_env_var_count() + WASM_SYMBOL_IMPORTED("ic0", "env_var_count"); + +uint32_t ic0_env_var_name_size(uint32_t index) + WASM_SYMBOL_IMPORTED("ic0", "env_var_name_size"); + +void ic0_env_var_name_copy(uint32_t index, uint32_t dst, uint32_t offset, + uint32_t size) + WASM_SYMBOL_IMPORTED("ic0", "env_var_name_copy"); + +uint32_t ic0_env_var_name_exists(uint32_t name_src, uint32_t name_size) + WASM_SYMBOL_IMPORTED("ic0", "env_var_name_exists"); + +uint32_t ic0_env_var_value_size(uint32_t name_src, uint32_t name_size) + WASM_SYMBOL_IMPORTED("ic0", "env_var_value_size"); + +void ic0_env_var_value_copy(uint32_t name_src, uint32_t name_size, + uint32_t dst, uint32_t offset, uint32_t size) + WASM_SYMBOL_IMPORTED("ic0", "env_var_value_copy"); + #ifdef __cplusplus } #endif diff --git a/src/cli/nicp_functions/new_impl.nim b/src/cli/nicp_functions/new_impl.nim index ea6f808..9943f97 100644 --- a/src/cli/nicp_functions/new_impl.nim +++ b/src/cli/nicp_functions/new_impl.nim @@ -223,11 +223,38 @@ networks: gateway: bind: "0.0.0.0" port: 8000 - ii: true # iiが有効になる。 http://id.ai.localhost:8000/#authorize + ii: true # http://id.ai.localhost:8000/#authorize environments: - name: local network: local + canisters: + - backend + - frontend + settings: + backend: + environment_variables: + APP_ENV: "local" + + - name: staging + network: ic + canisters: + - backend + - frontend + settings: + backend: + environment_variables: + APP_ENV: "staging" + + - name: production + network: ic + canisters: + - backend + - frontend + settings: + backend: + environment_variables: + APP_ENV: "production" """ else: result = """ @@ -242,11 +269,35 @@ networks: gateway: bind: "0.0.0.0" port: 8000 - ii: true # iiが有効になる。 http://id.ai.localhost:8000/#authorize + ii: true # http://id.ai.localhost:8000/#authorize environments: - name: local network: local + canisters: + - backend + settings: + backend: + environment_variables: + APP_ENV: "local" + + - name: staging + network: ic + canisters: + - backend + settings: + backend: + environment_variables: + APP_ENV: "staging" + + - name: production + network: ic + canisters: + - backend + settings: + backend: + environment_variables: + APP_ENV: "production" """ proc renderBackendReadme(): string = diff --git a/src/nicp_cdk.nim b/src/nicp_cdk.nim index b43ae50..ecf36d0 100644 --- a/src/nicp_cdk.nim +++ b/src/nicp_cdk.nim @@ -11,5 +11,6 @@ import ./nicp_cdk/ic_types/ic_text; export ic_text; import ./nicp_cdk/ic_types/ic_variant; export ic_variant; import ./nicp_cdk/ic_types/ic_func; export ic_func; import ./nicp_cdk/ic_api; export ic_api; +import ./nicp_cdk/environment; export environment; import ./nicp_cdk/async/ic_async; export ic_async; import ./nicp_cdk/canisters/management_canister; export management_canister; diff --git a/src/nicp_cdk/environment.nim b/src/nicp_cdk/environment.nim new file mode 100644 index 0000000..97f3427 --- /dev/null +++ b/src/nicp_cdk/environment.nim @@ -0,0 +1,42 @@ +import std/options + +import ./ic0/ic0 + +type IcEnv* = object + +proc contains*(_: type IcEnv, name: string): bool = + let cName = name.cstring + ic0_env_var_name_exists(cast[int](cName), name.len) != 0 + +proc get*(_: type IcEnv, name: string): Option[string] = + let cName = name.cstring + let nameSrc = cast[int](cName) + + # env_var_value_size traps when the name does not exist. + if ic0_env_var_name_exists(nameSrc, name.len) == 0: + return none(string) + + let size = ic0_env_var_value_size(nameSrc, name.len) + if size == 0: + return some("") + + var value = newString(size) + ic0_env_var_value_copy(nameSrc, name.len, cast[int](addr value[0]), 0, size) + some(value) + +proc getOrDefault*(_: type IcEnv, name: string, defaultValue: string): string = + IcEnv.get(name).get(defaultValue) + +proc names*(_: type IcEnv): seq[string] = + let count = ic0_env_var_count() + result = newSeqOfCap[string](count) + + for i in 0.. i32; // * s ic0.debug_print : (src : I, size : I) -> (); // * s ic0.trap : (src : I, size : I) -> (); // * s + // for env + ic0.env_var_count : () -> I; + ic0.env_var_name_size : (index : I) -> I; + ic0.env_var_name_copy : (index : I, dst : I, offset : I, size : I) -> (); + ic0.env_var_name_exists : (name_src : I, name_size : I) -> i32; + ic0.env_var_value_size : (name_src : I, name_size : I) -> I; + ic0.env_var_value_copy : (name_src : I, name_size : I, dst : I, offset : I, size : I) -> (); diff --git a/tests/config.nims b/tests/config.nims index 7413d03..eaf283c 100644 --- a/tests/config.nims +++ b/tests/config.nims @@ -1,3 +1,5 @@ +import os, strutils + switch("path", "$projectDir") switch("path", "$projectDir/..") switch("path", "$projectDir/../src")