Skip to content

Commit 69c1cfd

Browse files
committed
feat(xlings): tools gain a tier, and a dependency's declaration is installed
## Two halves of one defect `[xlings.workspace]` had one list. A board-support package naming both an emulator (to run) and a debug probe (to reach hardware) installed both for every consumer, including one that only wanted the library to compile — while package dependencies have had `[dependencies]` / `[build-dependencies]` / `[dev-dependencies]` since the beginning. [xlings.workspace] "xim:qemu-arm" = "9.2.4-1" # unchanged behaviour "xim:probe-rs" = { version = "0.24.0", when = "run" } [feature-xlings.hardware] "xim:probe-rs" = "0.24.0" # never fetched otherwise | when | installed by | reaches a consumer | |---|---|---| | (omitted) | every verb that builds | yes | | build | every verb that builds | yes | | run | `mcpp run`, `mcpp test` | yes | | dev | only the package that declared it, as the root | no | Omitting `when` is the pre-2026.9.4.2 behaviour exactly, so no manifest changes. The tier rides the entry rather than a second table, the shape `[dependencies]` already uses; `[feature-xlings.<f>]` is `[feature-deps.<f>]`'s spelling. ## ⚠️⚠️ And provisioning now spans the graph, because looking did already The previous release extended the runner's bare-name LOOKUP to every package in the graph and left provisioning at the root. That is a search of directories nothing installed into — a lookup that can only fail. Both are one expression now, so they cannot drift. Provisioning happens in two passes for an ordering reason: the root's list is needed before the graph exists (a toolchain may depend on it), and a dependency's is not known until resolution. The second pass provisions only what the first did not, under its own stamp, so an unchanged project still pays for no xlings round trip. ## ⚠️ The hazard a tier introduces, and the gate for it `mcpp build` installs less than `mcpp run` needs, and the run fast path exists precisely to skip the pass that would install the difference. The build cache records whether a build left a run-tier tool unprovisioned; `try_fast_run` declines such an entry, exactly as it declines one that records a runner. ## The criterion tests what mcpp REQUESTS, not what got installed Verifying a tier by installing needs a clean machine and a network. It is also the wrong object: what the tier changes is the SET, and `MCPP_NO_AUTO_INSTALL=1` refuses to provision while naming exactly that set. `tests/e2e/335` reads both commands on one project, so the criterion carries its own denominator — an mcpp that provisioned nothing, or a manifest that never parsed, fails the `run` half. ## Discoverability `mcpp why runners` lists what this project supplies, beside everything else it reports resolving; `mcpp run --list-runners` is the same read alone. No stamped one-time hint: a cache hit does not re-run, so it would be silent on the build that needed it most. 97/97 unit (5 new); e2e 88, 130-131, 327, 332-335 green.
1 parent 00dd2e4 commit 69c1cfd

17 files changed

Lines changed: 1054 additions & 253 deletions

CHANGELOG.md

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
66
## [2026.9.4.2] — 2026-09-04
77

8-
runner 有了名字,`--locked` 成为断言,`mcpp emit sbom`
8+
runner 有了名字,工具有了档位,`--locked` 成为断言,`mcpp emit sbom`
99

1010
### ⭐⭐ 一条命令,加具名的例外
1111

@@ -49,6 +49,47 @@ CycloneDX 1.5,覆盖**已记录**的解析。⚠️ 读锁而不是重新解析
4949
者不同的图的文档比没有更糟。归在 `emit` 之下而不是新开一级命令:`emit` 已经是
5050
「生成描述本工程的文档」。
5151

52+
### ⭐⭐ 工具有了档位,而且依赖声明的工具现在真的会被装
53+
54+
包依赖从一开始就有 `[dependencies]` / `[build-dependencies]` / `[dev-dependencies]`
55+
这条轴,工具只有一张表。一个同时点名模拟器与调试探针的板级包,会把两个都装给每一位
56+
消费者,包括只想把库编出来的那一位。
57+
58+
```toml
59+
[xlings.workspace]
60+
"xim:qemu-arm" = "9.2.4-1" # 不写就是从前的行为
61+
"xim:probe-rs" = { version = "0.24.0", when = "run" }
62+
63+
[feature-xlings.hardware]
64+
"xim:probe-rs" = "0.24.0" # 不要这个 feature 就永不下载
65+
```
66+
67+
| `when` | 由谁安装 | 传播到消费者 |
68+
|---|---|---|
69+
| *(不写)* | 每个构建命令 ||
70+
| `build` | 每个构建命令 ||
71+
| `run` | `mcpp run``mcpp test` ||
72+
| `dev` | 只有声明它的那个包作为根时 | **** |
73+
74+
**不写 `when` 保持今天的行为,所以没有迁移。**
75+
76+
⚠️⚠️ **同时:`[xlings.workspace]` 的供给扩到全图。** 在此之前只有根工程的声明会被
77+
安装,而查找(runner 按裸名找程序)已经跨全图 —— **在没有任何东西安装过的目录里
78+
查找,是只可能失败的查找**。两者现在由同一个表达式定义。
79+
80+
⚠️ 档位带来的一个危险已被堵上:`mcpp build` 装得比 `mcpp run` 需要的少,而 run 的
81+
快路径正是为跳过那一步存在的。构建缓存记下「这次构建留下了未安装的 run 档工具」,
82+
`mcpp run` 的快路径据此拒绝该条目 —— 与它拒绝声明了 runner 的条目同理。
83+
84+
判据不测「装成了没有」,而测**mcpp 要装什么**:`MCPP_NO_AUTO_INSTALL=1` 下拒绝供给
85+
**点名它本来要装的集合**,于是 `build``run` 两条命令的差集就是被测的性质,
86+
一次下载都不需要(`tests/e2e/335`)。
87+
88+
### 发现性
89+
90+
`mcpp why runners` 列出本工程提供的 runner,与其余解析结果并列;
91+
`mcpp run --list-runners` 是同一份读取,单独报告。
92+
5293
新增 `docs/18-devices.md``docs/19-supported-versions.md`(中英双份)。
5394
指令协议版本 6。
5495

docs/05-mcpp-toml.md

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1920,6 +1920,65 @@ triples say one, descriptors and xlings' project file say the other) and both
19201920
are accepted wherever a platform is named. A table with no key for this host
19211921
and no `default` declares nothing here.
19221922

1923+
#### `when` — which verbs need this tool (mcpp 2026.9.4.2+)
1924+
1925+
```toml
1926+
[xlings.workspace]
1927+
"xim:qemu-arm" = "9.2.4-1" # every build, as before
1928+
"xim:codegen" = { version = "1.0", when = "build" }
1929+
"xim:probe-rs" = { version = "0.24.0", when = "run" }
1930+
"xim:clang-tidy"= { version = "20", when = "dev" }
1931+
```
1932+
1933+
Package dependencies have had this axis since the beginning —
1934+
`[dependencies]`, `[build-dependencies]`, `[dev-dependencies]`. Tools had one
1935+
list, so a board-support package that named both an emulator and a debug probe
1936+
installed both for every consumer, including one that only wanted the library
1937+
to compile.
1938+
1939+
| `when` | Installed by | Reaches a consumer |
1940+
|---|---|---|
1941+
| *(omitted)* | every verb that builds | yes |
1942+
| `build` | every verb that builds | yes |
1943+
| `run` | `mcpp run`, `mcpp test` | yes |
1944+
| `dev` | only the package that declared it, as the root | **no** |
1945+
1946+
**Omitting `when` is the pre-2026.9.4.2 behaviour exactly**, so no manifest has
1947+
to change. Narrowing is optional; it is not a question an author has to answer.
1948+
1949+
`dev` is the only tier that does not propagate. It means *"while the package
1950+
that declared this is itself being developed"*, so a dependency's `dev` entry is
1951+
never installed for a consumer. Every other tier does reach one, which is the
1952+
point of a board package knowing its own machine: it declares the emulator once
1953+
and every consumer gets it.
1954+
1955+
The tier is written on the entry rather than as a second table, on the same
1956+
reasoning that makes `[dependencies]` accept both `dep = "1.0"` and
1957+
`dep = { version = "1.0", features = [...] }`. A scoped entry must name
1958+
`version` even to leave it empty (`version = ""` means *present, any version*),
1959+
because `{ when = "run" }` and a misspelt `version` key would otherwise be
1960+
indistinguishable.
1961+
1962+
#### `[feature-xlings.<feature>]` — a tool a feature needs
1963+
1964+
```toml
1965+
[features]
1966+
default = ["emulator"]
1967+
emulator = {}
1968+
hardware = {}
1969+
1970+
[feature-xlings.hardware]
1971+
"xim:probe-rs" = "0.24.0"
1972+
```
1973+
1974+
The same table, gated on a feature, spelled the way `[feature-deps.<feature>]`
1975+
is. A consumer who never asks for `hardware` never downloads a probe driver.
1976+
Entries here accept `when` exactly as the unconditional ones do.
1977+
1978+
A feature name no `[features]` table declares is reported as a schema warning:
1979+
it activates for nobody and installs nothing, and a tool whose absence is only
1980+
visible as *"the device is never reachable"* is the hardest kind to diagnose.
1981+
19231982
#### Which version a tool the project did not name resolves to
19241983

19251984
| The project declares | The version comes from |

docs/17-the-project-environment.md

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -151,7 +151,12 @@ exists for one host platform only is therefore declared for that platform
151151
on Linux and nothing elsewhere. The keys and the resolution rule are in
152152
chapter 5, §2.13.
153153

154-
**The runner.** A program under `[xlings] deps` is also where
154+
**Which verbs install it.** An entry may name a tier —
155+
`{ version = "0.24.0", when = "run" }` — and a `[feature-xlings.<feature>]`
156+
table gates one on a feature. A tool the project will not use is then not
157+
downloaded: chapter 5, §2.13. Omitting the tier is the historical behaviour.
158+
159+
**The runner.** A program under `[xlings.workspace]` is also where
155160
`[target.<triple>].runner` looks first for its first element, before `PATH`
156161
(chapter 5, §2.7.3). The two keys together provision a user-mode emulator on a
157162
CI host and execute a cross-built artifact through it, without the manifest
@@ -164,7 +169,8 @@ naming the payload's path.
164169
| a library the program links | `[dependencies]` |
165170
| the compiler | `[toolchain]`, chapter 3 |
166171
| a host tool a dependency produces | `tools = [...]`, chapter 7 |
167-
| a tool present in the environment | `[xlings] deps` |
172+
| a tool present in the environment | `[xlings.workspace]` |
173+
| a tool only one verb or one feature needs | `when = "run"`, `[feature-xlings.<f>]` |
168174
| which environment | `[xlings] subos` |
169175

170176
## 7. Related chapters

docs/18-devices.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ performs, with the artifact appended or substituted for `{}`.
2020
mcpp run # the default runner
2121
mcpp run --runner flash # a named one
2222
mcpp run --list-runners # what this project supplies
23+
mcpp why runners # the same list, beside everything else resolved
2324
```
2425

2526
`mcpp run` is the whole of the common case, including on real hardware. On a

docs/zh/05-mcpp-toml.md

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1637,6 +1637,57 @@ mcpp 既供给它——机器上没有就装,有就映射——也把它物化
16371637
文件说后者),**凡是点名平台的地方两者都接受**。表里既没有本机这一项也没有
16381638
`default`,就表示在这里什么都不声明。
16391639

1640+
#### `when` —— 哪些命令需要这个工具(mcpp 2026.9.4.2+)
1641+
1642+
```toml
1643+
[xlings.workspace]
1644+
"xim:qemu-arm" = "9.2.4-1" # 每次构建都装,与从前一样
1645+
"xim:codegen" = { version = "1.0", when = "build" }
1646+
"xim:probe-rs" = { version = "0.24.0", when = "run" }
1647+
"xim:clang-tidy" = { version = "20", when = "dev" }
1648+
```
1649+
1650+
包依赖从一开始就有这条轴 —— `[dependencies]``[build-dependencies]`
1651+
`[dev-dependencies]`。工具只有一张表,于是一个同时点名模拟器与调试探针的板级支持包
1652+
会把两个都装给每一位消费者,包括只想把库编出来的那一位。
1653+
1654+
| `when` | 由谁安装 | 是否传播到消费者 |
1655+
|---|---|---|
1656+
| *(不写)* | 每个构建命令 ||
1657+
| `build` | 每个构建命令 ||
1658+
| `run` | `mcpp run``mcpp test` ||
1659+
| `dev` | 只有声明它的那个包作为根时 | **** |
1660+
1661+
**不写 `when` 就是 2026.9.4.2 之前的行为**,所以没有任何清单需要改。收窄是可选动作,
1662+
不是作者必须回答的新问题。
1663+
1664+
`dev` 是唯一不传播的一档。它的含义是「声明它的那个包自己在被开发时」,所以依赖的
1665+
`dev` 条目永远不会为消费者安装。其余各档都会到达消费者 —— 这正是板级包知道自己机器
1666+
的意义:它声明一次模拟器,每一位消费者都拿得到。
1667+
1668+
档位写在**条目**上而不是另开一张表,理由与 `[dependencies]` 同时接受 `dep = "1.0"`
1669+
`dep = { version = "1.0", features = [...] }` 是同一条。带档位的条目**必须**写出
1670+
`version`,哪怕留空(`version = ""` 表示「存在即可,版本不限」)—— 否则
1671+
`{ when = "run" }` 与写错的 `version` 键无法区分。
1672+
1673+
#### `[feature-xlings.<feature>]` —— 某个 feature 才需要的工具
1674+
1675+
```toml
1676+
[features]
1677+
default = ["emulator"]
1678+
emulator = {}
1679+
hardware = {}
1680+
1681+
[feature-xlings.hardware]
1682+
"xim:probe-rs" = "0.24.0"
1683+
```
1684+
1685+
同一张表,按 feature 门控,拼法沿用 `[feature-deps.<feature>]`**不要 `hardware`
1686+
的消费者永远不会下载探针驱动。** 这里的条目同样接受 `when`
1687+
1688+
`[features]` 里没有声明过的 feature 名会作为 schema 警告报出:它对谁都不激活、什么
1689+
都不装,而这种工具的缺席只表现为「设备就是连不上」,是最难诊断的一种。
1690+
16401691
#### 工程没点名的工具,其版本的来源
16411692

16421693
| 工程声明了 | 版本来自 |

docs/zh/17-the-project-environment.md

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,11 @@ create/bootstrap that environment instead of falling back to active/default
122122
`deps = [{ linux = "qemu-user-aarch64" }]` 在 Linux 上声明这个模拟器,在别处什么都不声明。
123123
键与解析规则见第 5 章 §2.13。
124124

125-
**runner。** `[xlings] deps` 下的程序也是 `[target.<triple>].runner` 查找其第一个元素
125+
**哪些命令会安装它。** 一条条目可以带档位 —— `{ version = "0.24.0", when = "run" }` ——
126+
`[feature-xlings.<feature>]` 则把工具挂在某个 feature 上。用不到的工具因此不会被下载:
127+
见第 5 章 §2.13。不写档位就是从前的行为。
128+
129+
**runner。** `[xlings.workspace]` 下的程序也是 `[target.<triple>].runner` 查找其第一个元素
126130
的首选位置,在 `PATH` 之前(第 5 章 §2.7.3)。两个键合起来,在 CI 宿主上供给用户态模拟器,
127131
并通过它执行交叉构建的产物,而清单不必写出载荷的路径。
128132

@@ -133,7 +137,8 @@ create/bootstrap that environment instead of falling back to active/default
133137
| 程序链接的库 | `[dependencies]` |
134138
| 编译器 | `[toolchain]`,第 3 章 |
135139
| 依赖产出的宿主工具 | `tools = [...]`,第 7 章 |
136-
| 环境里要有的工具 | `[xlings] deps` |
140+
| 环境里要有的工具 | `[xlings.workspace]` |
141+
| 只有某个命令或某个 feature 需要的工具 | `when = "run"``[feature-xlings.<f>]` |
137142
| 用哪个环境 | `[xlings] subos` |
138143

139144
## 7. 相关章节

docs/zh/18-devices.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
mcpp run # 默认 runner
1616
mcpp run --runner flash # 具名的
1717
mcpp run --list-runners # 这个工程提供了哪些
18+
mcpp why runners # 同一份清单,与其余解析结果并列
1819
```
1920

2021
**`mcpp run` 覆盖了常见情形的全部,真实硬件也一样。** 在设备上,「运行一个程序」

modules/manifest/src/toml.cppm

Lines changed: 126 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -240,6 +240,69 @@ inline XlingsEntry parse_address(std::string_view address) {
240240
return XlingsEntry{ ns, target, std::string(version) };
241241
}
242242

243+
// An entry's value, split into the part that names a version and the tier that
244+
// part belongs to.
245+
//
246+
// ⭐ `{ version = "…", when = "…" }` IS THE SAME SHAPE A DEPENDENCY ALREADY
247+
// HAS. mcpp writes `dep = "1.0"` or `dep = { version = "1.0", features = [] }`,
248+
// and a tool entry now writes `"xim:probe-rs" = "0.24.0"` or
249+
// `{ version = "0.24.0", when = "run" }`. One table, richer entries — rather
250+
// than a second table per tier, which `[xlings.workspace]` was made the one
251+
// table specifically to avoid.
252+
//
253+
// The two spellings cannot be confused. A platform table's keys are `linux`,
254+
// `macosx`, `macos`, `windows` and `default`; the scoped table's are `version`
255+
// and `when`, and neither set contains a member of the other. So the presence
256+
// of either key decides, and the `version` it carries may itself be a platform
257+
// table.
258+
struct WhenSplit {
259+
const mcpp::libs::toml::Value* value = nullptr; // string, or platform table
260+
ToolWhen when = ToolWhen::Always;
261+
};
262+
263+
inline std::expected<ToolWhen, std::string> parse_when(std::string_view w) {
264+
if (w == "build") return ToolWhen::Build;
265+
if (w == "run") return ToolWhen::Run;
266+
if (w == "dev") return ToolWhen::Dev;
267+
if (w == "always") return ToolWhen::Always;
268+
return std::unexpected(std::format(
269+
"when = '{}' is not a tier; expected 'build', 'run' or 'dev' "
270+
"(omit it for the default, which is every build)", w));
271+
}
272+
273+
inline std::expected<WhenSplit, std::string>
274+
split_when(const mcpp::libs::toml::Value& v) {
275+
if (!v.is_table()) return WhenSplit{ &v, ToolWhen::Always };
276+
const auto& t = v.as_table();
277+
auto itVer = t.find("version");
278+
auto itWhen = t.find("when");
279+
if (itVer == t.end() && itWhen == t.end())
280+
return WhenSplit{ &v, ToolWhen::Always }; // a platform table
281+
// ⚠️ A SCOPED ENTRY MUST NAME ITS VERSION KEY EVEN TO LEAVE IT EMPTY.
282+
// `{ when = "run" }` alone reads as "present, unconstrained, run tier",
283+
// which is a meaningful thing to say — but so is a typo of `version`, and
284+
// the two would be indistinguishable. The key is required, `""` says
285+
// unconstrained, and a table carrying anything else is refused by name.
286+
for (auto const& [k, _] : t)
287+
if (k != "version" && k != "when")
288+
return std::unexpected(std::format(
289+
"unknown key '{}' in a scoped entry; expected 'version' and "
290+
"'when'", k));
291+
if (itVer == t.end())
292+
return std::unexpected(std::string(
293+
"a scoped entry needs 'version' (write version = \"\" for "
294+
"\"present, any version\")"));
295+
WhenSplit out{ &itVer->second, ToolWhen::Always };
296+
if (itWhen != t.end()) {
297+
if (!itWhen->second.is_string())
298+
return std::unexpected(std::string("'when' must be a string"));
299+
auto w = parse_when(itWhen->second.as_string());
300+
if (!w) return std::unexpected(w.error());
301+
out.when = *w;
302+
}
303+
return out;
304+
}
305+
243306
// Combine the two halves a namespace may be written on. Both may carry it;
244307
// disagreeing is an error rather than a precedence rule, because a precedence
245308
// rule would make one of the two spellings silently ineffective.
@@ -1518,7 +1581,10 @@ std::expected<Manifest, ManifestError> parse_string(std::string_view content,
15181581
// both rather than pick one.
15191582
std::map<std::string, std::string> writtenAs;
15201583
for (auto& [k, val] : *wt) {
1521-
auto vals = platform_values(val);
1584+
auto scoped = split_when(val);
1585+
if (!scoped) return std::unexpected(error(origin,
1586+
std::format("[xlings.workspace] {}: {}", k, scoped.error())));
1587+
auto vals = platform_values(*scoped->value);
15221588
if (!vals) return std::unexpected(error(origin,
15231589
std::format("[xlings.workspace] {}: {}", k, vals.error())));
15241590
// Every platform, for the descriptor emitter. Resolved per
@@ -1544,6 +1610,65 @@ std::expected<Manifest, ManifestError> parse_string(std::string_view content,
15441610
writtenAs.emplace(entry->target, k);
15451611
m.xlings.workspace[entry->target] = entry->pin();
15461612
m.xlings.deps.push_back(entry->address());
1613+
if (scoped->when != ToolWhen::Always)
1614+
m.xlings.depWhen[entry->address()] = scoped->when;
1615+
}
1616+
}
1617+
// `[feature-xlings.<feature>]` — the same table, gated on a feature of the
1618+
// package that declared it.
1619+
//
1620+
// ⭐ THE SPELLING IS `[feature-deps.<feature>]`'s, DELIBERATELY. A feature
1621+
// that pulls in a package and a feature that pulls in a tool are the same
1622+
// statement about the same feature, and inventing a second syntax for the
1623+
// second would make an author learn a rule that buys nothing. The keys
1624+
// differ because the things named differ: a package name there, an xim
1625+
// address here.
1626+
//
1627+
// ⚠️ A FEATURE NAME THAT NO `[features]` DECLARES IS REPORTED. A
1628+
// `[feature-xlings.hardwear]` activates for nobody and installs nothing,
1629+
// and the tool it names is the one whose absence is hardest to diagnose:
1630+
// the build succeeds and the device is simply never reachable.
1631+
//
1632+
// A warning rather than an error, on the same reasoning as the
1633+
// `[features]` schema check above — it is surfaced for the ROOT manifest
1634+
// before any dependency's is loaded, so a package may adopt a feature its
1635+
// consumers' engines do not yet know without breaking them.
1636+
if (auto* ft = doc->get_table("feature-xlings")) {
1637+
for (auto& [feature, tbl] : *ft) {
1638+
if (!m.featuresMap.contains(feature)) {
1639+
std::string known;
1640+
for (auto const& [f, _] : m.featuresMap) {
1641+
if (!known.empty()) known += ", ";
1642+
known += f;
1643+
}
1644+
m.schemaWarnings.push_back(std::format(
1645+
"[feature-xlings.{}] names a feature no [features] table "
1646+
"declares, so nothing it lists is ever installed. {}",
1647+
feature, known.empty()
1648+
? std::string("This package declares no features.")
1649+
: std::format("Declared features: {}.", known)));
1650+
}
1651+
if (!tbl.is_table())
1652+
return std::unexpected(error(origin, std::format(
1653+
"[feature-xlings.{}] must be a table of package = version",
1654+
feature)));
1655+
for (auto& [k, val] : tbl.as_table()) {
1656+
auto scoped = split_when(val);
1657+
if (!scoped) return std::unexpected(error(origin, std::format(
1658+
"[feature-xlings.{}] {}: {}", feature, k, scoped.error())));
1659+
auto vals = platform_values(*scoped->value);
1660+
if (!vals) return std::unexpected(error(origin, std::format(
1661+
"[feature-xlings.{}] {}: {}", feature, k, vals.error())));
1662+
auto hostValue = value_for_platform(*vals, host_platform_key());
1663+
if (!hostValue) continue; // not declared on this host
1664+
auto entry = make_xlings_entry(k, *hostValue);
1665+
if (!entry) return std::unexpected(error(origin, std::format(
1666+
"[feature-xlings.{}] {}: {}", feature, k, entry.error())));
1667+
m.xlings.featureDeps[feature].push_back(entry->address());
1668+
m.xlings.featurePins[entry->address()] = entry->pin();
1669+
if (scoped->when != ToolWhen::Always)
1670+
m.xlings.depWhen[entry->address()] = scoped->when;
1671+
}
15471672
}
15481673
}
15491674
if (doc->get("xlings.subos")) {

0 commit comments

Comments
 (0)