@typecad/framework-zephyr 1.0.0-alpha.13 → 1.0.0-alpha.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (191) hide show
  1. package/README.md +22 -35
  2. package/dist/as-built.d.ts +33 -0
  3. package/dist/as-built.js +188 -0
  4. package/dist/async/timer-polyfill.js +1 -1
  5. package/dist/boardgen.d.ts +128 -0
  6. package/dist/boardgen.js +1366 -0
  7. package/dist/chips/board-overrides.d.ts +7 -0
  8. package/dist/chips/board-overrides.js +11 -0
  9. package/dist/chips/controllers.d.ts +28 -8
  10. package/dist/chips/controllers.js +49 -12
  11. package/dist/chips/esp32s3.js +49 -0
  12. package/dist/chips/index.d.ts +6 -12
  13. package/dist/chips/index.js +18 -34
  14. package/dist/chips/resolve.d.ts +16 -2
  15. package/dist/chips/resolve.js +298 -15
  16. package/dist/chips/soc/.d.ts +2 -0
  17. package/dist/chips/soc/.js +129 -0
  18. package/dist/chips/soc/esp32.d.ts +2 -0
  19. package/dist/chips/soc/esp32.js +120 -0
  20. package/dist/chips/soc/esp32c3.d.ts +2 -0
  21. package/dist/chips/soc/esp32c3.js +90 -0
  22. package/dist/chips/soc/esp32c6.d.ts +2 -0
  23. package/dist/chips/soc/esp32c6.js +109 -0
  24. package/dist/chips/soc/esp32s3.d.ts +2 -0
  25. package/dist/chips/soc/esp32s3.js +189 -0
  26. package/dist/chips/soc/index.d.ts +2 -0
  27. package/dist/chips/soc/index.js +23 -0
  28. package/dist/chips/soc/nrf52840.d.ts +2 -0
  29. package/dist/chips/soc/nrf52840.js +130 -0
  30. package/dist/chips/soc/rp2040.d.ts +2 -0
  31. package/dist/chips/soc/rp2040.js +141 -0
  32. package/dist/chips/soc/rp2350a.d.ts +2 -0
  33. package/dist/chips/soc/rp2350a.js +145 -0
  34. package/dist/chips/soc/samd21g18a.d.ts +2 -0
  35. package/dist/chips/soc/samd21g18a.js +143 -0
  36. package/dist/chips/soc/stm32f411xe.d.ts +2 -0
  37. package/dist/chips/soc/stm32f411xe.js +251 -0
  38. package/dist/chips/types.d.ts +382 -12
  39. package/dist/chips/xiao-ble.js +33 -0
  40. package/dist/display/profiles.js +1 -1
  41. package/dist/doctor.js +38 -4
  42. package/dist/dt-config/custom-board.d.ts +23 -0
  43. package/dist/dt-config/custom-board.js +227 -0
  44. package/dist/dt-config/kconfig.d.ts +83 -1
  45. package/dist/dt-config/kconfig.js +116 -27
  46. package/dist/dt-config/overlay.js +623 -18
  47. package/dist/framework.manifest.d.ts +20 -30
  48. package/dist/framework.manifest.js +124 -178
  49. package/dist/index.d.ts +4 -1
  50. package/dist/index.js +8 -2
  51. package/dist/lowering/adc.d.ts +26 -5
  52. package/dist/lowering/adc.js +137 -31
  53. package/dist/lowering/ble.js +31 -28
  54. package/dist/lowering/dac.js +9 -9
  55. package/dist/lowering/fs.js +18 -10
  56. package/dist/lowering/gpio.d.ts +2 -0
  57. package/dist/lowering/gpio.js +90 -10
  58. package/dist/lowering/http.js +39 -14
  59. package/dist/lowering/hwtimer.d.ts +4 -0
  60. package/dist/lowering/hwtimer.js +35 -10
  61. package/dist/lowering/i2c.js +46 -54
  62. package/dist/lowering/index.d.ts +5 -5
  63. package/dist/lowering/index.js +15 -16
  64. package/dist/lowering/interrupts.d.ts +22 -5
  65. package/dist/lowering/interrupts.js +104 -22
  66. package/dist/lowering/mqtt.js +12 -6
  67. package/dist/lowering/power.js +0 -4
  68. package/dist/lowering/preferences.js +49 -77
  69. package/dist/lowering/pulse.js +5 -36
  70. package/dist/lowering/pwm.d.ts +32 -3
  71. package/dist/lowering/pwm.js +105 -17
  72. package/dist/lowering/random.js +3 -3
  73. package/dist/lowering/sensor.d.ts +37 -0
  74. package/dist/lowering/sensor.js +119 -0
  75. package/dist/lowering/spi.d.ts +12 -0
  76. package/dist/lowering/spi.js +65 -40
  77. package/dist/lowering/thread.d.ts +14 -0
  78. package/dist/lowering/thread.js +55 -0
  79. package/dist/lowering/timing.js +20 -46
  80. package/dist/lowering/tone.js +18 -5
  81. package/dist/lowering/uart.d.ts +13 -0
  82. package/dist/lowering/uart.js +72 -51
  83. package/dist/lowering/usb.d.ts +34 -0
  84. package/dist/lowering/usb.js +206 -0
  85. package/dist/lowering/wdt.d.ts +1 -1
  86. package/dist/lowering/wdt.js +17 -7
  87. package/dist/lowering/wifi.d.ts +3 -2
  88. package/dist/lowering/wifi.js +120 -111
  89. package/dist/sdk/board-catalog-sync.d.ts +16 -0
  90. package/dist/sdk/board-catalog-sync.js +41 -0
  91. package/dist/sdk/board-data.generated.d.ts +2 -0
  92. package/dist/sdk/board-data.generated.js +4 -0
  93. package/dist/sdk/catalog-walker.d.ts +90 -0
  94. package/dist/sdk/catalog-walker.js +682 -0
  95. package/dist/sdk/dts-reader.d.ts +83 -0
  96. package/dist/sdk/dts-reader.js +596 -0
  97. package/dist/strategy.d.ts +136 -17
  98. package/dist/strategy.js +881 -212
  99. package/dist/toolchain/bossac-touch.d.ts +18 -0
  100. package/dist/toolchain/bossac-touch.js +88 -0
  101. package/dist/toolchain/compat.d.ts +2 -2
  102. package/dist/toolchain/compat.js +8 -3
  103. package/dist/toolchain/debug-config.d.ts +21 -26
  104. package/dist/toolchain/debug-config.js +212 -72
  105. package/dist/toolchain/env-check.d.ts +4 -4
  106. package/dist/toolchain/env-check.js +5 -5
  107. package/dist/toolchain/index.d.ts +66 -1
  108. package/dist/toolchain/index.js +713 -58
  109. package/dist/toolchain/scaffold.d.ts +9 -0
  110. package/dist/toolchain/scaffold.js +84 -7
  111. package/dist/toolchain/west-discover.d.ts +8 -1
  112. package/dist/toolchain/west-discover.js +19 -6
  113. package/dist/toolchain/west-spawn.js +17 -5
  114. package/installer/CHANGELOG.md +473 -0
  115. package/installer/README.md +328 -0
  116. package/installer/environment.yml +46 -0
  117. package/installer/etc/conda/activate.d/zephyr.bat +7 -0
  118. package/installer/etc/conda/activate.d/zephyr.ps1 +12 -0
  119. package/installer/etc/conda/activate.d/zephyr.sh +20 -0
  120. package/installer/etc/conda/deactivate.d/zephyr.bat +6 -0
  121. package/installer/etc/conda/deactivate.d/zephyr.ps1 +6 -0
  122. package/installer/etc/conda/deactivate.d/zephyr.sh +4 -0
  123. package/installer/install.mjs +580 -0
  124. package/installer/install.ps1 +619 -0
  125. package/installer/install.sh +310 -0
  126. package/installer/lib/detect-platform.sh +62 -0
  127. package/installer/lib/fetch-sdk.sh +304 -0
  128. package/installer/lib/init-workspace.sh +153 -0
  129. package/installer/lib/write-activation.sh +76 -0
  130. package/installer/patches/tfm-c-include-separators.patch +19 -0
  131. package/installer/patches/zephyr-tfm-nordic-zephyr-include.patch +24 -0
  132. package/installer/templates/project/.typecad/activate-zephyr.ps1 +31 -0
  133. package/installer/templates/project/.typecad/activate-zephyr.sh +31 -0
  134. package/installer/templates/project/.vscode/settings.json +30 -0
  135. package/installer/templates/project/README.md +58 -0
  136. package/installer/versions.env +110 -0
  137. package/package.json +16 -16
  138. package/src/as-built.ts +206 -0
  139. package/src/boardgen.ts +1428 -0
  140. package/src/chips/controllers.ts +61 -12
  141. package/src/chips/index.ts +35 -54
  142. package/src/chips/resolve.ts +316 -14
  143. package/src/chips/types.ts +388 -13
  144. package/src/display/profiles.ts +1 -1
  145. package/src/doctor.ts +110 -77
  146. package/src/dt-config/custom-board.ts +325 -0
  147. package/src/dt-config/kconfig.ts +177 -19
  148. package/src/dt-config/overlay.ts +1058 -450
  149. package/src/framework.manifest.ts +125 -178
  150. package/src/index.ts +57 -41
  151. package/src/lowering/adc.ts +222 -102
  152. package/src/lowering/ble.ts +32 -28
  153. package/src/lowering/dac.ts +82 -82
  154. package/src/lowering/fs.ts +18 -10
  155. package/src/lowering/gpio.ts +242 -152
  156. package/src/lowering/http.ts +512 -488
  157. package/src/lowering/hwtimer.ts +139 -101
  158. package/src/lowering/i2c.ts +116 -126
  159. package/src/lowering/index.ts +91 -93
  160. package/src/lowering/interrupts.ts +113 -21
  161. package/src/lowering/mqtt.ts +12 -6
  162. package/src/lowering/preferences.ts +327 -354
  163. package/src/lowering/pwm.ts +167 -74
  164. package/src/lowering/random.ts +3 -3
  165. package/src/lowering/sensor.ts +155 -0
  166. package/src/lowering/spi.ts +164 -124
  167. package/src/lowering/thread.ts +63 -0
  168. package/src/lowering/timing.ts +46 -72
  169. package/src/lowering/uart.ts +73 -51
  170. package/src/lowering/usb.ts +221 -0
  171. package/src/lowering/wdt.ts +95 -84
  172. package/src/lowering/wifi.ts +120 -111
  173. package/src/sdk/board-catalog-sync.ts +62 -0
  174. package/src/strategy.ts +888 -229
  175. package/src/toolchain/bossac-touch.ts +112 -0
  176. package/src/toolchain/compat.ts +8 -3
  177. package/src/toolchain/debug-config.ts +666 -522
  178. package/src/toolchain/env-check.ts +9 -9
  179. package/src/toolchain/index.ts +765 -44
  180. package/src/toolchain/scaffold.ts +81 -7
  181. package/src/toolchain/west-discover.ts +334 -316
  182. package/src/toolchain/west-spawn.ts +17 -5
  183. package/src/async/timer-polyfill.ts +0 -107
  184. package/src/chips/esp32.ts +0 -74
  185. package/src/chips/esp32s3.ts +0 -57
  186. package/src/chips/xiao-ble.ts +0 -70
  187. package/src/lowering/power.ts +0 -70
  188. package/src/lowering/pulse.ts +0 -93
  189. package/src/lowering/tone.ts +0 -60
  190. package/src/lowering/worker-backing.ts +0 -94
  191. package/src/lowering/worker.ts +0 -17
@@ -0,0 +1,310 @@
1
+ #!/usr/bin/env bash
2
+ # ---------------------------------------------------------------------------
3
+ # typeCAD Zephyr installer — POSIX bootstrap (Linux, macOS, Git Bash on Windows).
4
+ #
5
+ # One-command install of a working Zephyr build environment via micromamba:
6
+ # 1. download the micromamba static binary (no preinstalled conda/Python needed)
7
+ # 2. create a conda env with the host build tools (west, cmake, ninja, dtc, ...)
8
+ # 3. install activation hooks that set ZEPHYR_BASE + ZEPHYR_SDK_INSTALL_DIR
9
+ # 4. fetch + extract the official Zephyr SDK full bundle (cross-toolchains)
10
+ # 5. west init + west update a vanilla Zephyr workspace
11
+ #
12
+ # After install: `micromamba activate zephyr` and framework-zephyr's west
13
+ # discovery cascade (packages/framework-zephyr/src/toolchain/west-discover.ts)
14
+ # picks up west via PATH + ZEPHYR_BASE automatically — no manual env vars.
15
+ #
16
+ # Usage:
17
+ # bash install.sh [--dry-run] [--no-sdk] [--no-workspace]
18
+ # [--env-name NAME] [--sdk-version VER] [--help]
19
+ #
20
+ # --dry-run print the resolved plan (URLs, paths, versions) and exit;
21
+ # downloads/creates nothing. Used by the test suite.
22
+ # --no-sdk skip the Zephyr SDK download (env + workspace only).
23
+ # --no-workspace skip west init/update (env + SDK only).
24
+ # --env-name override the conda env name (default: zephyr).
25
+ # --sdk-version override the Zephyr SDK version (default: pinned in versions.env).
26
+ # ---------------------------------------------------------------------------
27
+ set -eu
28
+
29
+ PKG_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
30
+
31
+ # --- flag parsing -----------------------------------------------------------
32
+ DRY_RUN=0
33
+ DO_SDK=1
34
+ DO_WORKSPACE=1
35
+ ENV_NAME_OVERRIDE=""
36
+ SDK_VERSION_OVERRIDE=""
37
+ PLATFORMS=""
38
+
39
+ usage() {
40
+ cat <<'EOF'
41
+ Usage: bash install.sh [options]
42
+
43
+ --dry-run Print the resolved plan (URLs, paths, versions) and exit.
44
+ Downloads/creates nothing.
45
+ --no-sdk Skip the Zephyr SDK download (env + workspace only).
46
+ --no-workspace Skip west init/update (env + SDK only).
47
+ --modify SDK platforms only: re-run selection and ADD the selected
48
+ platforms with missing toolchains. Implies --no-workspace.
49
+ --prune With --modify: also DELETE toolchains of platforms not in
50
+ the selection (reclaim disk space). Without it, --modify
51
+ is purely additive.
52
+ --platforms SEL Comma-separated platform groups (arm,esp32,riscv,arc,rx,x86,aarch64)
53
+ or 'all' for the full bundle. Default: all — RECOMMENDED:
54
+ every board in the data pack builds as-is.
55
+ --env-name NAME Override the conda env name (default: zephyr).
56
+ --sdk-version V Override the Zephyr SDK version (default: pinned in versions.env).
57
+ -h, --help Show this help.
58
+
59
+ Environment overrides:
60
+ MAMBA_ROOT_PREFIX micromamba root (default: ~/micromamba)
61
+ WORKSPACE_DIR west workspace (default: ~/zephyrproject)
62
+ SDK_INSTALL_PARENT where the SDK extracts (default: inside the conda env)
63
+ EOF
64
+ }
65
+
66
+ while [ $# -gt 0 ]; do
67
+ case "$1" in
68
+ --dry-run) DRY_RUN=1 ;;
69
+ --no-sdk) DO_SDK=0 ;;
70
+ --no-workspace) DO_WORKSPACE=0 ;;
71
+ --modify|-m) DO_WORKSPACE=0 ;;
72
+ --prune) PRUNE=1 ;;
73
+ --platforms) PLATFORMS="${2:?--platforms needs a value}"; shift ;;
74
+ --env-name) ENV_NAME_OVERRIDE="${2:?--env-name needs a value}"; shift ;;
75
+ --sdk-version) SDK_VERSION_OVERRIDE="${2:?--sdk-version needs a value}"; shift ;;
76
+ -h|--help) usage; exit 0 ;;
77
+ *) echo "install: unknown flag: $1" >&2; usage >&2; exit 2 ;;
78
+ esac
79
+ shift
80
+ done
81
+
82
+ : "${PLATFORMS:=all}"
83
+ export PRUNE="${PRUNE:-0}"
84
+
85
+ # --- load pinned versions + library functions -------------------------------
86
+ # shellcheck source=versions.env
87
+ . "$PKG_DIR/versions.env"
88
+ # shellcheck source=lib/detect-platform.sh
89
+ . "$PKG_DIR/lib/detect-platform.sh"
90
+ # shellcheck source=lib/fetch-sdk.sh
91
+ . "$PKG_DIR/lib/fetch-sdk.sh"
92
+ # shellcheck source=lib/init-workspace.sh
93
+ . "$PKG_DIR/lib/init-workspace.sh"
94
+ # shellcheck source=lib/write-activation.sh
95
+ . "$PKG_DIR/lib/write-activation.sh"
96
+
97
+ # Apply CLI overrides.
98
+ [ -n "$ENV_NAME_OVERRIDE" ] && ENV_NAME="$ENV_NAME_OVERRIDE"
99
+ [ -n "$SDK_VERSION_OVERRIDE" ] && ZEPHYR_SDK_VERSION="$SDK_VERSION_OVERRIDE"
100
+
101
+ # --- platform detection -----------------------------------------------------
102
+ detect_platform
103
+
104
+ # --- path resolution --------------------------------------------------------
105
+ : "${MAMBA_ROOT_PREFIX:=$HOME/micromamba}"
106
+ : "${WORKSPACE_DIR:=$HOME/zephyrproject}"
107
+ ENV_PREFIX="$MAMBA_ROOT_PREFIX/envs/$ENV_NAME"
108
+ # SDK lives OUTSIDE the env prefix: `micromamba create` requires the prefix
109
+ # empty, and a failed create must not trap a multi-GB SDK download.
110
+ : "${SDK_INSTALL_PARENT:=$MAMBA_ROOT_PREFIX/zephyr-sdk}"
111
+ ZEPHYR_SDK_INSTALL_DIR="$SDK_INSTALL_PARENT/zephyr-sdk-$ZEPHYR_SDK_VERSION"
112
+
113
+ # micromamba binary lives at different paths on POSIX vs Windows (the win-64
114
+ # tarball ships Library/bin/micromamba.exe, not bin/micromamba).
115
+ case "$MAMBA_PLAT" in
116
+ win-*) : "${MAMBA:=$MAMBA_ROOT_PREFIX/Library/bin/micromamba.exe}" ;;
117
+ *) : "${MAMBA:=$MAMBA_ROOT_PREFIX/bin/micromamba}" ;;
118
+ esac
119
+
120
+ # --- plan printer (greppable: every line tagged [plan]) ---------------------
121
+ print_plan() {
122
+ local bundle="zephyr-sdk-${ZEPHYR_SDK_VERSION}_${SDK_PLAT}${ZEPHYR_SDK_BUNDLE_SUFFIX}.${ARCHIVE_EXT}"
123
+ echo "[plan] typeCAD Zephyr installer"
124
+ echo "[plan] env name: $ENV_NAME"
125
+ echo "[plan] conda subdir: $MAMBA_PLAT"
126
+ echo "[plan] sdk platform: $SDK_PLAT"
127
+ echo "[plan] sdk version: $ZEPHYR_SDK_VERSION"
128
+ echo "[plan] platforms: $PLATFORMS"
129
+ echo "[plan] sdk bundle: $bundle"
130
+ echo "[plan] sdk bundle url: $SDK_RELEASE_BASE/v$ZEPHYR_SDK_VERSION/$bundle"
131
+ echo "[plan] micromamba url: $MICROMAMBA_BASE/$MAMBA_PLAT/latest"
132
+ echo "[plan] micromamba bin: $MAMBA"
133
+ echo "[plan] env prefix: $ENV_PREFIX"
134
+ echo "[plan] sdk install dir: $ZEPHYR_SDK_INSTALL_DIR"
135
+ echo "[plan] workspace dir: $WORKSPACE_DIR"
136
+ echo "[plan] zephyr base: $WORKSPACE_DIR/zephyr"
137
+ echo "[plan] manifest url: $ZEPHYR_MANIFEST_URL"
138
+ echo "[plan] manifest rev: $ZEPHYR_MANIFEST_REV"
139
+ echo "[plan] do sdk: $DO_SDK"
140
+ echo "[plan] do workspace: $DO_WORKSPACE"
141
+ }
142
+
143
+ print_plan
144
+
145
+ if [ "$DRY_RUN" = "1" ]; then
146
+ echo "[plan] DRY-RUN — no downloads, no env created."
147
+ exit 0
148
+ fi
149
+
150
+ # --- 1. micromamba ----------------------------------------------------------
151
+ download_micromamba() {
152
+ if [ -x "$MAMBA" ]; then
153
+ echo "micromamba: already present at $MAMBA"
154
+ return 0
155
+ fi
156
+ echo "micromamba: downloading ($MAMBA_PLAT)..."
157
+ mkdir -p "$MAMBA_ROOT_PREFIX"
158
+ curl -Ls "$MICROMAMBA_BASE/$MAMBA_PLAT/latest" | tar -xvj -C "$MAMBA_ROOT_PREFIX"
159
+ if [ ! -x "$MAMBA" ]; then
160
+ # Windows tarballs may land at a different inner path; locate it as a fallback.
161
+ local found
162
+ found="$(find "$MAMBA_ROOT_PREFIX" -name 'micromamba*' -type f 2>/dev/null | head -1 || true)"
163
+ [ -n "$found" ] && MAMBA="$found"
164
+ fi
165
+ if [ ! -x "$MAMBA" ]; then
166
+ echo "micromamba: download failed — binary not found under $MAMBA_ROOT_PREFIX" >&2
167
+ return 1
168
+ fi
169
+ echo "micromamba: installed at $MAMBA"
170
+ }
171
+
172
+ download_micromamba
173
+ export MAMBA_ROOT_PREFIX
174
+
175
+ # Register the micromamba shell hook in ~/.bashrc so `micromamba` is on PATH
176
+ # and `micromamba activate <env>` works in new bash shells. micromamba lands
177
+ # under $MAMBA_ROOT_PREFIX/bin, which is not on PATH by default.
178
+ echo "micromamba: registering shell hook in ~/.bashrc..."
179
+ "$MAMBA" shell init -s bash -r "$MAMBA_ROOT_PREFIX" \
180
+ || echo "micromamba: shell init (bash) failed — non-fatal; run it manually" >&2
181
+
182
+ # --- 2. conda env (host tools) ---------------------------------------------
183
+ create_env() {
184
+ if [ -d "$ENV_PREFIX" ] && "$MAMBA" env list 2>/dev/null | grep -q "^${ENV_NAME} "; then
185
+ echo "env: '$ENV_NAME' already exists at $ENV_PREFIX — updating"
186
+ "$MAMBA" env update -y -f "$PKG_DIR/environment.yml" -p "$ENV_PREFIX" || {
187
+ echo "env: env update failed" >&2; return 1; }
188
+ elif [ -e "$ENV_PREFIX" ]; then
189
+ # Prefix dir exists but micromamba doesn't list it as an env (no conda-meta).
190
+ # `create` would abort with "Non-conda folder exists at prefix". Surface it
191
+ # rather than auto-deleting a dir that may hold a multi-GB SDK.
192
+ echo "env: ERROR — '$ENV_PREFIX' exists but is not a valid conda env (no conda-meta)." >&2
193
+ echo "env: Usually left by a prior failed run. Remove it and re-run:" >&2
194
+ echo "env: rm -rf '$ENV_PREFIX'" >&2
195
+ return 1
196
+ else
197
+ echo "env: creating '$ENV_NAME' from environment.yml..."
198
+ "$MAMBA" create -y -f "$PKG_DIR/environment.yml" -n "$ENV_NAME" || {
199
+ echo "env: env create failed" >&2; return 1; }
200
+ fi
201
+ }
202
+
203
+ # Platform-limited host tools that can't live in environment.yml (micromamba
204
+ # ignores `# [not win]` selectors, so they'd break the solve on Windows).
205
+ # dtc — conda-forge has it for all POSIX subdirs. Needed for devicetree.
206
+ # openocd — conda-forge has it ONLY for linux-64 / osx-64 (no arm64, no win).
207
+ # Both are optional for the env to exist; we warn on failure so the env stays
208
+ # usable rather than aborting the whole install over a flashing/debug helper.
209
+ install_platform_tools() {
210
+ if [[ "$MAMBA_PLAT" == win-* ]]; then
211
+ echo "env: Windows host via install.sh — skipping POSIX-only tools."
212
+ echo "env: (On Windows, use 'node install.mjs' / install.ps1, which installs 7zip instead.)"
213
+ return 0
214
+ fi
215
+ echo "env: installing POSIX host tools (dtc, openocd)..."
216
+ if ! "$MAMBA" install -y -n "$ENV_NAME" -c conda-forge dtc >/dev/null 2>&1; then
217
+ echo "env: WARNING — dtc not installed on $MAMBA_PLAT." >&2
218
+ echo "env: Zephyr builds needing the C devicetree compiler will fail until dtc is available." >&2
219
+ fi
220
+ if ! "$MAMBA" install -y -n "$ENV_NAME" -c conda-forge openocd >/dev/null 2>&1; then
221
+ echo "env: note — openocd not available on $MAMBA_PLAT (only x86_64 POSIX); skipped." >&2
222
+ echo "env: only needed for JTAG/SWD flashing; nRF boards use nrfjprog/pyocd." >&2
223
+ fi
224
+ # dfu-util — west flash's dfu-util runner (STM32 ROM DFU bootloader boards,
225
+ # e.g. the WeAct Black Pill) shells out to it; a missing executable makes
226
+ # the runner die with a raw FileNotFoundError. Not on conda-forge either, so
227
+ # fall back to the system package manager. No auto-sudo: warn with the
228
+ # right command instead of failing the install over a flashing helper.
229
+ if ! command -v dfu-util >/dev/null 2>&1; then
230
+ echo "env: dfu-util not found — west flash on DFU-bootloader boards (STM32) needs it." >&2
231
+ case "$(uname -s)" in
232
+ Linux)
233
+ if command -v apt-get >/dev/null 2>&1; then
234
+ echo "env: install with: sudo apt-get install dfu-util" >&2
235
+ elif command -v dnf >/dev/null 2>&1; then
236
+ echo "env: install with: sudo dnf install dfu-util" >&2
237
+ elif command -v pacman >/dev/null 2>&1; then
238
+ echo "env: install with: sudo pacman -S dfu-util" >&2
239
+ else
240
+ echo "env: install dfu-util via your distribution's package manager." >&2
241
+ fi ;;
242
+ Darwin)
243
+ echo "env: install with: brew install dfu-util" >&2 ;;
244
+ *)
245
+ echo "env: install dfu-util via your package manager (https://dfu-util.sourceforge.net)." >&2 ;;
246
+ esac
247
+ fi
248
+ # bossac — west flash's bossac runner (SAMD SAM-BA bootloader boards — the
249
+ # Arduino Nano 33 IoT, Zero, MKR series) shells out to it, and Zephyr's
250
+ # FindHostTools resolves find_program(BOSSAC) at build-configure time, so it
251
+ # must be on PATH BEFORE building. In neither the Zephyr SDK nor conda-forge;
252
+ # no auto-sudo — warn with the right command instead.
253
+ if ! command -v bossac >/dev/null 2>&1; then
254
+ echo "env: bossac not found — west flash on SAM-BA-bootloader boards (SAMD) needs it." >&2
255
+ case "$(uname -s)" in
256
+ Linux)
257
+ if command -v apt-get >/dev/null 2>&1; then
258
+ echo "env: install with: sudo apt-get install bossa-cli" >&2
259
+ else
260
+ echo "env: install bossac via your distribution's package manager, or build it: https://github.com/shumatech/BOSSA" >&2
261
+ fi ;;
262
+ Darwin)
263
+ echo "env: install with: brew install bossa" >&2 ;;
264
+ *)
265
+ echo "env: install bossac via your package manager (https://github.com/shumatech/BOSSA)." >&2 ;;
266
+ esac
267
+ fi
268
+ }
269
+
270
+ create_env
271
+ install_platform_tools
272
+
273
+ # --- 3. activation hooks ---------------------------------------------------
274
+ write_activation
275
+
276
+ # --- 4. Zephyr SDK ---------------------------------------------------------
277
+ if [ "$DO_SDK" = "1" ]; then
278
+ fetch_sdk
279
+ else
280
+ echo "install: --no-sdk — skipping Zephyr SDK download"
281
+ fi
282
+
283
+ # --- 5. west workspace -----------------------------------------------------
284
+ if [ "$DO_WORKSPACE" = "1" ]; then
285
+ init_workspace
286
+ else
287
+ echo "install: --no-workspace — skipping west init/update"
288
+ fi
289
+
290
+ # --- done -------------------------------------------------------------------
291
+ cat <<EOF
292
+
293
+ install: done.
294
+
295
+ The micromamba shell hook has been added to ~/.bashrc.
296
+ Open a NEW shell (or run 'source ~/.bashrc'), then:
297
+ micromamba activate $ENV_NAME
298
+
299
+ Verify:
300
+ west --version
301
+ echo \$ZEPHYR_BASE # -> $WORKSPACE_DIR/zephyr
302
+
303
+ For the CURRENT session without a new shell:
304
+ eval "\$("$MAMBA" shell hook --shell bash)"
305
+ micromamba activate $ENV_NAME
306
+
307
+ Then a typeCAD/cuttlefish Zephyr build should discover west automatically:
308
+ cd <project> && npx cuttlefish build
309
+
310
+ EOF
@@ -0,0 +1,62 @@
1
+ #!/usr/bin/env bash
2
+ # ---------------------------------------------------------------------------
3
+ # detect-platform.sh — map uname output to conda + Zephyr SDK platform tokens.
4
+ # Sourced by install.sh. Defines detect_platform(), which exports:
5
+ #
6
+ # MAMBA_PLAT conda subdir: linux-64 | linux-aarch64 | osx-64 | osx-arm64 | win-64
7
+ # SDK_PLAT Zephyr SDK bundle platform: linux-x86_64 | linux-aarch64 |
8
+ # macos-x86_64 | macos-aarch64 | windows-x86_64
9
+ # ARCHIVE_EXT archive type for the SDK bundle: tar.xz (POSIX) | 7z (Windows)
10
+ #
11
+ # Returns non-zero on unsupported platforms. The conda arch token is OS-
12
+ # dependent (x86_64 -> "64" everywhere; arm64 is "arm64" on macOS but
13
+ # "aarch64" on Linux), so it is built from an explicit case table rather
14
+ # than a string substitution.
15
+ # ---------------------------------------------------------------------------
16
+
17
+ detect_platform() {
18
+ local os arch
19
+ os="$(uname -s)"
20
+ arch="$(uname -m)"
21
+
22
+ local mamba_os sdk_os
23
+ case "$os" in
24
+ Linux*) mamba_os=linux; sdk_os=linux ;;
25
+ Darwin*) mamba_os=osx; sdk_os=macos ;;
26
+ MINGW*|MSYS*|CYGWIN*) mamba_os=win; sdk_os=windows ;;
27
+ *)
28
+ echo "detect-platform: unsupported OS: $os (expected Linux, macOS, or Windows/Git Bash)" >&2
29
+ return 1
30
+ ;;
31
+ esac
32
+
33
+ local sdk_arch
34
+ case "$arch" in
35
+ x86_64|amd64) sdk_arch=x86_64 ;;
36
+ aarch64|arm64) sdk_arch=aarch64 ;;
37
+ *)
38
+ echo "detect-platform: unsupported arch: $arch (expected x86_64 or aarch64/arm64)" >&2
39
+ return 1
40
+ ;;
41
+ esac
42
+
43
+ case "${mamba_os}-${arch}" in
44
+ linux-x86_64|osx-x86_64|win-x86_64) MAMBA_PLAT="${mamba_os}-64" ;;
45
+ linux-aarch64|linux-arm64) MAMBA_PLAT="linux-aarch64" ;;
46
+ osx-arm64) MAMBA_PLAT="osx-arm64" ;;
47
+ *)
48
+ echo "detect-platform: no conda subdir maps to ${mamba_os}-${arch}" >&2
49
+ return 1
50
+ ;;
51
+ esac
52
+
53
+ SDK_PLAT="${sdk_os}-${sdk_arch}"
54
+
55
+ if [ "$sdk_os" = "windows" ]; then
56
+ ARCHIVE_EXT="7z"
57
+ else
58
+ ARCHIVE_EXT="tar.xz"
59
+ fi
60
+
61
+ export MAMBA_PLAT SDK_PLAT ARCHIVE_EXT
62
+ }
@@ -0,0 +1,304 @@
1
+ #!/usr/bin/env bash
2
+ # ---------------------------------------------------------------------------
3
+ # fetch-sdk.sh — download, verify, and extract the Zephyr SDK.
4
+ # Sourced by install.sh. Defines fetch_sdk(), which reads:
5
+ #
6
+ # ZEPHYR_SDK_VERSION, SDK_RELEASE_BASE (versions.env)
7
+ # SDK_PLAT, ARCHIVE_EXT (detect_platform)
8
+ # SHA256_<platform-with-underscores> (versions.env; TODO = unpinned)
9
+ # SDK_INSTALL_PARENT (resolved by install.sh)
10
+ # DRY_RUN (install.sh flag)
11
+ # PLATFORMS (install.sh flag or install.mjs
12
+ # checklist — comma-separated group
13
+ # ids, or "all" for the full bundle)
14
+ #
15
+ # On success the SDK is extracted to $SDK_INSTALL_PARENT/zephyr-sdk-<version>/.
16
+ #
17
+ # SELECTIVE MODE (PLATFORMS != "all"): downloads the ~10MB minimal bundle
18
+ # (cmake config + sdk_version) then only the selected toolchains
19
+ # (~100-300MB per group). Idempotent per-toolchain: a target dir that already
20
+ # exists is skipped, so --modify can add platforms incrementally without
21
+ # re-downloading everything.
22
+ #
23
+ # We deliberately do NOT run the SDK's setup.sh: the Zephyr docs endorse
24
+ # setting ZEPHYR_SDK_INSTALL_DIR as the alternative to setup.sh's CMake
25
+ # registry registration. That avoids sudo, the interactive toolchain prompt,
26
+ # and any host-tools install (host tools come from the conda env instead).
27
+ # ---------------------------------------------------------------------------
28
+
29
+ # Cross-platform sha256: sha256sum on Linux/Git Bash, shasum -a 256 on macOS.
30
+ _compute_sha256() {
31
+ local file="$1"
32
+ if command -v sha256sum >/dev/null 2>&1; then
33
+ sha256sum "$file" | awk '{print $1}'
34
+ else
35
+ shasum -a 256 "$file" | awk '{print $1}'
36
+ fi
37
+ }
38
+
39
+ # Download helper shared by the bundle + toolchain paths.
40
+ _download() {
41
+ local url="$1" archive="$2"
42
+ if command -v curl >/dev/null 2>&1; then
43
+ curl -fL --retry 3 -C - -o "$archive" "$url" \
44
+ || { echo "fetch-sdk: curl download failed" >&2; return 1; }
45
+ elif command -v wget >/dev/null 2>&1; then
46
+ wget -c -O "$archive" "$url" \
47
+ || { echo "fetch-sdk: wget download failed" >&2; return 1; }
48
+ else
49
+ echo "fetch-sdk: need curl or wget on PATH" >&2
50
+ return 1
51
+ fi
52
+ }
53
+
54
+ # Verify (skip with a computed-hash printout when not yet pinned).
55
+ _verify() {
56
+ local archive="$1" expected="$2"
57
+ local actual
58
+ actual="$(_compute_sha256 "$archive")"
59
+ if [ "$expected" = "TODO" ] || [ -z "$expected" ]; then
60
+ echo "fetch-sdk: WARNING — SHA256 not pinned (TODO in versions.env); skipping verification." >&2
61
+ echo "fetch-sdk: computed sha256 (pin this in versions.env to enforce): $actual"
62
+ else
63
+ if [ "$actual" != "$expected" ]; then
64
+ echo "fetch-sdk: SHA256 mismatch!" >&2
65
+ echo "fetch-sdk: expected: $expected" >&2
66
+ echo "fetch-sdk: actual: $actual" >&2
67
+ rm -f "$archive"
68
+ return 1
69
+ fi
70
+ echo "fetch-sdk: sha256 OK ($actual)"
71
+ fi
72
+ }
73
+
74
+ # Extract helper. ARCHIVE_EXT is .tar.xz (POSIX) or .7z (Windows).
75
+ _extract() {
76
+ local archive="$1" dest="$2"
77
+ case "$ARCHIVE_EXT" in
78
+ tar.xz)
79
+ tar -xf "$archive" -C "$dest" \
80
+ || { echo "fetch-sdk: tar extract failed" >&2; return 1; }
81
+ ;;
82
+ 7z)
83
+ local sevenz
84
+ sevenz="$(command -v 7z || command -v 7za || true)"
85
+ if [ -z "$sevenz" ]; then
86
+ echo "fetch-sdk: need 7z to extract a .7z bundle" >&2
87
+ echo "fetch-sdk: the conda env provides it (7zip in environment.yml); activate the env first" >&2
88
+ return 1
89
+ fi
90
+ "$sevenz" x -y -o"$dest" "$archive" >/dev/null \
91
+ || { echo "fetch-sdk: 7z extract failed" >&2; return 1; }
92
+ ;;
93
+ *)
94
+ echo "fetch-sdk: unknown archive extension: $ARCHIVE_EXT" >&2
95
+ return 1
96
+ ;;
97
+ esac
98
+ }
99
+
100
+ # List installed toolchain targets by scanning a directory. Used by the
101
+ # --modify delta (installed-but-not-selected → removed). The xtensa- prefixed
102
+ # pattern matters: xtensa targets are multi-segment
103
+ # (xtensa-espressif_esp32s3_zephyr-elf) and a single-segment regex misses them.
104
+ _list_installed_toolchains() {
105
+ local dir="$1"
106
+ [ -d "$dir" ] || return 0
107
+ (cd "$dir" && ls -d */ 2>/dev/null | sed 's|/$||' | grep -E '^(xtensa-)?.*-zephyr-(eabi|elf)$' || true)
108
+ }
109
+
110
+ fetch_sdk() {
111
+ local sdk="$SDK_INSTALL_PARENT/zephyr-sdk-${ZEPHYR_SDK_VERSION}"
112
+ local platforms="${PLATFORMS:-all}"
113
+ # 1.0.x bundle flavor suffix (_gnu = all GNU toolchains + host tools); empty
114
+ # for 0.17.x "full" bundles. Individual toolchain tarballs carry the flavor
115
+ # as an infix (toolchain_gnu_<plat>_<target>) when set.
116
+ local suffix="${ZEPHYR_SDK_BUNDLE_SUFFIX:-}"
117
+ local tc_infix=""
118
+ [ -n "$suffix" ] && tc_infix="${suffix#_}_"
119
+ echo "fetch-sdk: platform selection: $platforms"
120
+
121
+ # ── FULL BUNDLE MODE (platforms = "all") ────────────────────────────────────
122
+ if [ "$platforms" = "all" ]; then
123
+ local bundle="zephyr-sdk-${ZEPHYR_SDK_VERSION}_${SDK_PLAT}${suffix}.${ARCHIVE_EXT}"
124
+ local url="${SDK_RELEASE_BASE}/v${ZEPHYR_SDK_VERSION}/${bundle}"
125
+ local checksum_var="SHA256_$(echo "$SDK_PLAT" | tr '-' '_')"
126
+ local expected="${!checksum_var:-TODO}"
127
+
128
+ if [ "$expected" = "NONE" ]; then
129
+ echo "fetch-sdk: ERROR — the Zephyr SDK ${ZEPHYR_SDK_VERSION} publishes no build for ${SDK_PLAT}." >&2
130
+ echo "fetch-sdk: (macOS Intel has no 1.0.x SDK.) Use an ARM Mac, or pin the older" >&2
131
+ echo "fetch-sdk: SDK line: --sdk-version 0.17.4" >&2
132
+ return 1
133
+ fi
134
+
135
+ echo "fetch-sdk: downloading full bundle $bundle"
136
+ echo "fetch-sdk: url: $url"
137
+ echo "fetch-sdk: sha256: ${expected}"
138
+
139
+ if [ "$DRY_RUN" = "1" ]; then
140
+ echo "fetch-sdk: [DRY-RUN] would download $url"
141
+ echo "fetch-sdk: [DRY-RUN] would extract into $SDK_INSTALL_PARENT"
142
+ echo "fetch-sdk: [DRY-RUN] would set ZEPHYR_SDK_INSTALL_DIR=$sdk"
143
+ return 0
144
+ fi
145
+
146
+ if [ -e "$sdk/sdk_version" ]; then
147
+ echo "fetch-sdk: SDK already present at $sdk — skipping download"
148
+ return 0
149
+ fi
150
+
151
+ mkdir -p "$SDK_INSTALL_PARENT"
152
+ local archive="${SDK_INSTALL_PARENT}/${bundle}"
153
+ _download "$url" "$archive" || return 1
154
+ _verify "$archive" "$expected" || return 1
155
+ _extract "$archive" "$SDK_INSTALL_PARENT" || return 1
156
+ rm -f "$archive"
157
+ echo "fetch-sdk: done — $sdk"
158
+ return 0
159
+ fi
160
+
161
+ # ── SELECTIVE MODE (minimal bundle + individual toolchains) ─────────────────
162
+ local sel_checksum="SHA256_$(echo "$SDK_PLAT" | tr '-' '_')"
163
+ if [ "${!sel_checksum:-TODO}" = "NONE" ]; then
164
+ echo "fetch-sdk: ERROR — the Zephyr SDK ${ZEPHYR_SDK_VERSION} publishes no build for ${SDK_PLAT}." >&2
165
+ echo "fetch-sdk: (macOS Intel has no 1.0.x SDK.) Use an ARM Mac, or pin the older" >&2
166
+ echo "fetch-sdk: SDK line: --sdk-version 0.17.4" >&2
167
+ return 1
168
+ fi
169
+ local minimal="zephyr-sdk-${ZEPHYR_SDK_VERSION}_${SDK_PLAT}_minimal.${ARCHIVE_EXT}"
170
+ local minimal_url="${SDK_RELEASE_BASE}/v${ZEPHYR_SDK_VERSION}/${minimal}"
171
+
172
+ if [ "$DRY_RUN" = "1" ]; then
173
+ echo "fetch-sdk: [DRY-RUN] would download minimal bundle $minimal_url (~10 MB)"
174
+ echo "fetch-sdk: [DRY-RUN] selected platforms: $platforms"
175
+ local grp
176
+ for grp in ${platforms//,/ }; do
177
+ local targets_var="PLATFORM_${grp}"
178
+ echo "fetch-sdk: [DRY-RUN] ${grp}: ${!targets_var:-<unknown group>}"
179
+ done
180
+ echo "fetch-sdk: [DRY-RUN] would write .typecad-platforms state file"
181
+ return 0
182
+ fi
183
+
184
+ mkdir -p "$SDK_INSTALL_PARENT"
185
+
186
+ # 1. Minimal bundle (cmake/ + sdk_version + sdk_toolchains — ~10 MB).
187
+ if [ -e "$sdk/sdk_version" ]; then
188
+ echo "fetch-sdk: SDK base already present at $sdk — skipping minimal bundle"
189
+ else
190
+ echo "fetch-sdk: downloading minimal bundle (~10 MB)"
191
+ local minimal_archive="${SDK_INSTALL_PARENT}/${minimal}"
192
+ _download "$minimal_url" "$minimal_archive" || return 1
193
+ # The minimal bundle's SHA256 is small enough that a mismatch is unlikely;
194
+ # verify if pinned but don't fail on TODO.
195
+ _verify "$minimal_archive" "TODO" || true
196
+ _extract "$minimal_archive" "$SDK_INSTALL_PARENT" || return 1
197
+ rm -f "$minimal_archive"
198
+ fi
199
+
200
+ # 1.0.x installs toolchains under <sdk>/gnu/ — the SDK's own setup.sh does
201
+ # `cd gnu && tar -x`, and its cmake globs ${ZEPHYR_SDK_INSTALL_DIR}/gnu/*
202
+ # (generic.cmake: TOOLCHAIN_HOME=${ZEPHYR_SDK_INSTALL_DIR}/gnu). 0.17.x used
203
+ # the SDK root. Toolchains misplaced at the root by older installers are
204
+ # migrated (moved) into gnu/ instead of re-downloaded. Idempotency checks
205
+ # <target>/bin — a bare <target>/ dir can be a hollow leftover of a failed
206
+ # setup.sh/setup.cmd toolchain download.
207
+ local tc_root="$sdk"
208
+ local sdk_ng=0
209
+ if [ -n "$suffix" ]; then
210
+ tc_root="$sdk/gnu"
211
+ sdk_ng=1
212
+ fi
213
+ mkdir -p "$tc_root"
214
+
215
+ # 2. Individual toolchains for each selected platform group.
216
+ local grp targets target
217
+ for grp in ${platforms//,/ }; do
218
+ local targets_var="PLATFORM_${grp}"
219
+ targets="${!targets_var:-}"
220
+ if [ -z "$targets" ]; then
221
+ echo "fetch-sdk: WARNING — unknown platform group '$grp' (no PLATFORM_${grp} in versions.env); skipping" >&2
222
+ continue
223
+ fi
224
+ echo "fetch-sdk: platform '${grp}':"
225
+ for target in $targets; do
226
+ # Migrate / clean a misplaced SDK-root copy from pre-1.0.x-layout installs.
227
+ if [ "$sdk_ng" -eq 1 ] && [ -d "$sdk/$target" ]; then
228
+ if [ -d "$tc_root/$target/bin" ]; then
229
+ echo "fetch-sdk: $target — removing misplaced SDK-root copy (pre-1.0.x-layout install)"
230
+ rm -rf "$sdk/$target"
231
+ else
232
+ echo "fetch-sdk: $target — migrating misplaced SDK-root copy into gnu/"
233
+ mv "$sdk/$target" "$tc_root/$target"
234
+ fi
235
+ continue
236
+ fi
237
+ if [ -d "$tc_root/$target/bin" ]; then
238
+ echo "fetch-sdk: $target — already installed, skipping"
239
+ continue
240
+ fi
241
+ # A target dir without bin/ is a hollow leftover (e.g. a failed SDK
242
+ # setup.sh download) — remove it so the fresh extract lands clean.
243
+ if [ -d "$tc_root/$target" ]; then
244
+ echo "fetch-sdk: $target — hollow toolchain dir (no bin/), re-downloading"
245
+ rm -rf "$tc_root/$target"
246
+ fi
247
+ # 1.0.x names individual toolchains toolchain_gnu_<plat>_<target>; 0.17.x
248
+ # used toolchain_<plat>_<target>. tc_infix carries the flavor when set.
249
+ local tc_bundle="toolchain_${tc_infix}${SDK_PLAT}_${target}.${ARCHIVE_EXT}"
250
+ local tc_url="${SDK_RELEASE_BASE}/v${ZEPHYR_SDK_VERSION}/${tc_bundle}"
251
+ echo "fetch-sdk: $target — downloading ${tc_bundle}"
252
+ local tc_archive="${SDK_INSTALL_PARENT}/${tc_bundle}"
253
+ _download "$tc_url" "$tc_archive" || return 1
254
+ _verify "$tc_archive" "TODO" || true
255
+ # Toolchain archives are flat (<target>/bin/...); extract into the
256
+ # toolchain root (gnu/ on 1.0.x, the SDK root on 0.17.x).
257
+ _extract "$tc_archive" "$tc_root" || return 1
258
+ rm -f "$tc_archive"
259
+ done
260
+ done
261
+
262
+ # 3. --prune only: remove installed toolchains that are NOT in the new
263
+ # selection. Without --prune the modify step is purely additive —
264
+ # unselected toolchains stay on disk so an existing install never
265
+ # loses anything because of a narrower re-run. On 1.0.x scan both the
266
+ # gnu/ root and the SDK root so misplaced copies get cleaned up too.
267
+ if [ "${PRUNE:-0}" = "1" ]; then
268
+ local installed="$(_list_installed_toolchains "$tc_root")"
269
+ [ "$sdk_ng" -eq 1 ] && installed="$installed $(_list_installed_toolchains "$sdk")"
270
+ local selected_targets=""
271
+ for grp in ${platforms//,/ }; do
272
+ local targets_var="PLATFORM_${grp}"
273
+ selected_targets="$selected_targets ${!targets_var:-}"
274
+ done
275
+ for target in $installed; do
276
+ if ! echo " $selected_targets " | grep -q " $target "; then
277
+ echo "fetch-sdk: removing deselected toolchain: $target"
278
+ rm -rf "$tc_root/$target" "$sdk/$target"
279
+ fi
280
+ done
281
+ fi
282
+
283
+ # 4. Persist the selection. Without --prune the marker records the UNION
284
+ # of what was here before and the new selection — the marker must
285
+ # describe what is actually on disk.
286
+ local marker_path="$sdk/.typecad-platforms"
287
+ local marker_sel="$platforms"
288
+ if [ "${PRUNE:-0}" != "1" ] && [ -f "$marker_path" ]; then
289
+ local prev union="" seen="" grp
290
+ prev="$(cat "$marker_path")"
291
+ for grp in $(echo "$prev,$platforms" | tr ',' ' '); do
292
+ grp="$(echo "$grp" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')"
293
+ [ -z "$grp" ] && continue
294
+ [ "$grp" = "all" ] && continue
295
+ case " $seen " in
296
+ *" $grp "*) ;;
297
+ *) seen="$seen $grp"; union="${union:+$union,}$grp" ;;
298
+ esac
299
+ done
300
+ [ -n "$union" ] && marker_sel="$union"
301
+ fi
302
+ echo "$marker_sel" > "$marker_path"
303
+ echo "fetch-sdk: done — $sdk (platforms: $marker_sel)"
304
+ }