mirror of
https://github.com/logos-co/logos-module-builder.git
synced 2026-08-27 10:11:10 +00:00
feat(metadata): codegen.consumer_api_style, gated on how the image gets tokens
Lets a module declare its CONSUMER type surface independently of its provider
packaging. A cdylib-packaged module can now hold Qt-typed dependency wrappers —
the combination that was previously inexpressible, and the only reason
`interface: "provider"` and `--provider-header` are still alive.
The enabling codegen landed first (logos-cpp-sdk 620f2e1, logos-qt-sdk 6b88630):
a default-constructible Qt umbrella and a consumer binding that takes an
explicit origin instead of a LogosAPI. An earlier attempt at this key FAILED
because it was added without that codegen, so its only reachable outcome was a
compile error inside generated code. It is meaningful now.
── The predicate ───────────────────────────────────────────────────────────
packagedAsCdylib = interface == "cdylib"
|| (interface == "universal" && type != "ui_qml")
character-for-character what modulePreConfigure.autoCodegen branches on when it
decides to emit the module-impl C ABI, so the two cannot drift.
The distinction is NOT "this image has no LogosAPI" — a cdylib module's plugin
does contain one, in the Qt glue that receives tokens. What separates the two
worlds is where the image's own TokenManager is FILLED:
cdylib-packaged : logos_module_accept_token -> lp_token_save, same image.
An origin-bound wrapper's null sync hook costs nothing.
Qt plugin : only LpBridge::syncTokens, installed exclusively by
forTarget(api, …). An origin-bound wrapper there would be
silently unauthenticated — the exact defect syncTokens
exists for.
Measured with `nm -gU`, not argued: `logos_module_accept_token` is defined in
exactly the shapes the predicate calls true, across six real plugins.
`universal` + `ui_qml` is the trap — it looks cdylib-shaped but uiCodegen emits
only view glue, so it is a Qt object holding a LogosAPI. Excluded.
`--binding origin` is not selectable from metadata at all: it is derived as
`isQt && packagedAsCdylib` in the backend, an AND no key can reach from the
wrong side. What a key CAN express wrongly is the mirror move — `lp` on a Qt
plugin — unsafe for the identical reason, and refused at eval naming the key.
Verified additive on seven modules spanning legacy/core, universal/core,
universal/ui_qml and QML-only: `diff -r` clean, narHash identical where
comparable, and `test_qml_only` byte-identical by store path. Note compiled
modules cannot share a store path across any builder edit, since mkLogosModule
bakes LOGOS_MODULE_BUILDER_ROOT — narHash is the honest comparison.
Three mutation controls, each failing loudly: removing the eval throw, nulling
the backend's own assertion, and dropping the packagedAsCdylib conjunct from
`originBound` — the last being the one that pins the token-mirror hazard rather
than just the lp refusal.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
c60d4a9cf3
commit
08ae7ac8c1
@@ -116,6 +116,7 @@ the impl header/class are derived from `name` (e.g. `my_module` →
|
||||
| `impl_header` | `universal` | Path to the impl header (default `src/<name>_impl.h`) |
|
||||
| `impl_class` | `universal` | Impl class name (default PascalCase of `<name>` + `Impl`) |
|
||||
| `rep` | `ui_qml` + `universal` | Path to the `.rep` QtRO contract for a C++ UI backend |
|
||||
| `consumer_api_style` | `universal` / `cdylib` | Type surface of the generated `modules().<dep>` wrappers: `"lp"` (Qt-free, the default there) or `"qt"` |
|
||||
|
||||
```json
|
||||
"interface": "universal",
|
||||
@@ -134,6 +135,47 @@ For a universal C++ UI backend (`"type": "ui_qml"` + `"interface": "universal"`)
|
||||
"codegen": { "rep": "src/my_ui.rep" }
|
||||
```
|
||||
|
||||
#### `codegen.consumer_api_style`
|
||||
|
||||
Two independent axes meet in a module: the surface it PROVIDES (`interface`)
|
||||
and the surface it CONSUMES its dependencies through. This key names the second
|
||||
one, and only the second one — setting it changes nothing about the module's own
|
||||
API or its contract.
|
||||
|
||||
The default is derived and matches what the build always did:
|
||||
|
||||
| Module shape | Default | Wrappers |
|
||||
|--------------|---------|----------|
|
||||
| `interface: "universal"` (`type` other than `ui_qml`) | `lp` | `std::string` / `int64_t`, calling the logos-protocol C ABI |
|
||||
| `interface: "cdylib"` | `lp` | as above |
|
||||
| `interface: "universal"` + `type: "ui_qml"` | `qt` | `QString` / `qlonglong`, bound through the backend's `LogosAPI` |
|
||||
| omitted `interface` (hand-written Qt) | `qt` | as above |
|
||||
|
||||
Only ONE override is accepted, and it is the reason the key exists: a module
|
||||
packaged as a cdylib provider may ask for `"qt"`.
|
||||
|
||||
```json
|
||||
"interface": "universal",
|
||||
"codegen": { "consumer_api_style": "qt" }
|
||||
```
|
||||
|
||||
That module keeps its Qt-free PROVIDER surface — the std-typed impl header, the
|
||||
derived LIDL contract, the generated `logos_module_impl.h` C ABI — while
|
||||
`modules().<dep>` hands out Qt-typed wrappers. Those wrappers hold no
|
||||
`LogosAPI`: the umbrella is default-constructible and bakes `metadata.json#name`
|
||||
as the call origin, which each wrapper threads into
|
||||
`logos::qt::LpBridge::forOrigin(origin, target)`.
|
||||
|
||||
The reverse — asking a Qt PLUGIN (a hand-written module, or a `ui_qml` backend)
|
||||
for `"lp"` — is **refused at evaluation**, by name. Both LogosAPI-free wrapper
|
||||
flavours rely on something else populating the `TokenManager` their lp client
|
||||
reads. A cdylib image gets that over the C ABI (`logos_module_accept_token` →
|
||||
`lp_token_save`); a Qt plugin image does not — the host writes tokens to the
|
||||
`TokenManager` in its OWN image, and only `logos::qt::LpBridge::syncTokens`
|
||||
(installed exclusively by the LogosAPI-taking `forTarget`) mirrors them across.
|
||||
Without that mirror every outbound call presents an empty auth token and comes
|
||||
back as a default value with no error raised.
|
||||
|
||||
### `category`
|
||||
**Type:** string
|
||||
**Default:** `"general"`
|
||||
|
||||
@@ -598,8 +598,27 @@ let
|
||||
# generator default — no flag). Every other interface keeps qt.
|
||||
# (Only consulted in the source layout; nix builds get apiStyle from
|
||||
# the backend's --general-only call.)
|
||||
#
|
||||
# `config.consumer_api_style` (parseMetadata.nix — the resolved
|
||||
# `codegen.consumer_api_style`) is what makes this an override rather
|
||||
# than a pure derivation. It only ever REMOVES the flag: a
|
||||
# cdylib-packaged module that asks for the Qt consumer surface must
|
||||
# not have `lp` forced on it here. It is deliberately NOT allowed to
|
||||
# ADD one — the trigger condition below is character-for-character
|
||||
# today's, so no module that passes no flag today starts passing one
|
||||
# (a `cdylib` module never got this flag even though the nix backend
|
||||
# types it `lp`; unifying that would change every cdylib module's
|
||||
# derivation for a flag only the legacy source layout reads).
|
||||
#
|
||||
# There is no `--binding` counterpart here on purpose: this branch of
|
||||
# LogosModule.cmake never invokes logos-qt-generator at all, so it
|
||||
# cannot emit the origin-bound wrapper SET that the origin-bound
|
||||
# umbrella needs. The Qt consumer surface for a cdylib module is a
|
||||
# nix-build capability; the source layout keeps the one shape it can
|
||||
# actually produce.
|
||||
apiStyleCmakeFlags =
|
||||
if config.interface == "universal" && (config.type or "core") != "ui_qml"
|
||||
&& config.consumer_api_style == "lp"
|
||||
then [ "-DLOGOS_API_STYLE=lp" ]
|
||||
else [];
|
||||
# The backend only knows about Qt + logosModule (interface.h).
|
||||
|
||||
+110
-2
@@ -10,11 +10,106 @@
|
||||
raw = builtins.fromJSON jsonContent;
|
||||
nix = raw.nix or {};
|
||||
safeList = val: if builtins.isList val then val else [];
|
||||
|
||||
# The two fields the consumer axis below turns on, hoisted so the
|
||||
# attrset's own `interface` / `type` and the derivation of
|
||||
# `consumer_api_style` cannot drift apart (this set is not `rec`).
|
||||
moduleName_ = raw.name or "?";
|
||||
type_ = raw.type or "core";
|
||||
interface_ = raw.interface or "legacy";
|
||||
codegen_ = let c = raw.codegen or {}; in if builtins.isAttrs c then c else {};
|
||||
|
||||
# ── Is this module's own image a cdylib PROVIDER? ─────────────────────
|
||||
#
|
||||
# True exactly when modulePreConfigure.autoCodegen gives the module the
|
||||
# module-impl C ABI export surface (logos_module_impl.h) in the SAME
|
||||
# image its generated consumer wrappers are compiled into:
|
||||
#
|
||||
# interface "cdylib" -> cdylibCodegen
|
||||
# interface "universal", type != "ui_qml" -> universalCodegen
|
||||
# (a header-first cdylib)
|
||||
#
|
||||
# and false for the two shapes that are Qt PLUGIN objects holding a
|
||||
# LogosAPI: `interface: "legacy"` (handcrafted Qt) and
|
||||
# `interface: "universal"` + `type: "ui_qml"` (a view backend, which is
|
||||
# not a module — uiCodegen emits only the view glue).
|
||||
#
|
||||
# This predicate is deliberately the SAME expression autoCodegen
|
||||
# branches on, because the thing it decides is where this image's auth
|
||||
# TOKENS come from:
|
||||
#
|
||||
# cdylib — `logos_module_accept_token` -> `lp_token_save`, straight
|
||||
# into the TokenManager this image's outbound lp client
|
||||
# reads (logos-cpp-sdk lidl_gen_cdylib.cpp; the Rust
|
||||
# equivalent in logos-rust-sdk rustgen_provider.rs). A
|
||||
# consumer wrapper here needs NO LogosAPI.
|
||||
# Qt plugin — the host writes to the TokenManager in ITS image; the
|
||||
# plugin links its own copy of the protocol library, so the
|
||||
# tokens have to be MIRRORED across
|
||||
# (logos::qt::LpBridge::syncTokens, reached only via
|
||||
# `forTarget(api, ...)`). A consumer wrapper here that holds
|
||||
# no LogosAPI authenticates as nobody, and the call comes
|
||||
# back as a default value with no error raised.
|
||||
packagedAsCdylib_ =
|
||||
interface_ == "cdylib"
|
||||
|| (interface_ == "universal" && type_ != "ui_qml");
|
||||
|
||||
# ── codegen.consumer_api_style: "qt" | "lp" ───────────────────────────
|
||||
#
|
||||
# Which TYPE SURFACE this module's generated dependency wrappers expose
|
||||
# (`modules().<dep>` and the LogosModules umbrella). Independent of the
|
||||
# module's own PROVIDER surface, which `interface` alone decides.
|
||||
#
|
||||
# The default is exactly the value the build derived before this key
|
||||
# existed (logos-plugin-qt buildPlugin.nix's `apiStyle`), so an existing
|
||||
# metadata.json produces a byte-identical build.
|
||||
#
|
||||
# Only ONE override is reachable, and `packagedAsCdylib_` is why:
|
||||
#
|
||||
# cdylib + "qt" — ALLOWED, and the point of this key. The wrappers
|
||||
# come out Qt-typed and origin-bound (`--binding origin`): they hold
|
||||
# no LogosAPI and state this module's own name as the call origin.
|
||||
# Correct here precisely because tokens arrive over the C ABI.
|
||||
# Qt plugin + "lp" — REFUSED below. The lp wrappers hold no LogosAPI
|
||||
# either, and in a Qt plugin image nothing would ever populate the
|
||||
# TokenManager they read.
|
||||
consumerApiStyleDerived_ = if packagedAsCdylib_ then "lp" else "qt";
|
||||
consumerApiStyleDeclared_ = codegen_.consumer_api_style or null;
|
||||
consumerApiStyle_ =
|
||||
if consumerApiStyleDeclared_ == null then consumerApiStyleDerived_
|
||||
else if !(builtins.isString consumerApiStyleDeclared_)
|
||||
|| !(builtins.elem consumerApiStyleDeclared_ [ "qt" "lp" ]) then
|
||||
throw ("metadata.json: module '${moduleName_}' sets codegen.consumer_api_style = "
|
||||
+ "${builtins.toJSON consumerApiStyleDeclared_}. Valid values are \"qt\" "
|
||||
+ "(Qt-typed dependency wrappers) and \"lp\" (Qt-free, the logos-protocol "
|
||||
+ "C ABI). Omit the key to get this module's default (\"${consumerApiStyleDerived_}\").")
|
||||
else if consumerApiStyleDeclared_ == "lp" && !packagedAsCdylib_ then
|
||||
throw ''
|
||||
metadata.json: module '${moduleName_}' sets codegen.consumer_api_style = "lp",
|
||||
which is only available to a module packaged as a cdylib provider.
|
||||
|
||||
This module declares interface "${interface_}" with type "${type_}", so its
|
||||
generated dependency wrappers are compiled into a Qt PLUGIN object that holds a
|
||||
LogosAPI and exports no `logos_module_accept_token`. The lp wrappers call the
|
||||
logos-protocol C ABI directly and hold no LogosAPI, so NOTHING would populate the
|
||||
TokenManager they read: every outbound call would present an empty auth token,
|
||||
capability_module would refuse to mint a per-target token, and the call would
|
||||
return a default value with no error surfaced. That is the exact defect
|
||||
`logos::qt::LpBridge::syncTokens` exists to prevent — which is why the two
|
||||
consumer surfaces are not freely interchangeable.
|
||||
|
||||
Reachable values for this module: "qt" (its default; omit the key).
|
||||
|
||||
To get the Qt-free consumer surface, make the module a cdylib provider —
|
||||
`interface: "universal"` (write a plain src/${moduleName_}_impl.h and the contract
|
||||
is derived from it) or `interface: "cdylib"`.
|
||||
''
|
||||
else consumerApiStyleDeclared_;
|
||||
in {
|
||||
# Runtime fields
|
||||
name = raw.name or (throw "metadata.json must specify 'name'");
|
||||
version = raw.version or "1.0.0";
|
||||
type = raw.type or "core";
|
||||
type = type_;
|
||||
category = raw.category or "general";
|
||||
description = raw.description or "A Logos module";
|
||||
main = raw.main or null;
|
||||
@@ -123,7 +218,20 @@
|
||||
# Module API style: "legacy" (default), "universal" (pure C++ + generated
|
||||
# Qt glue), "cdylib" (module-impl C ABI + generated glue).
|
||||
# "provider" was removed; autoCodegen throws if a module still declares it.
|
||||
interface = raw.interface or "legacy";
|
||||
interface = interface_;
|
||||
|
||||
# Where this module's generated consumer wrappers get compiled, as a
|
||||
# boolean the backends can consult without re-deriving it: true = into
|
||||
# the cdylib provider image (tokens arrive over the module-impl C ABI),
|
||||
# false = into a Qt plugin object holding a LogosAPI (tokens have to be
|
||||
# mirrored). Derived in the `let` above, next to the reasoning.
|
||||
packaged_as_cdylib = packagedAsCdylib_;
|
||||
|
||||
# The resolved consumer type surface ("qt" | "lp") — the default derived
|
||||
# from `packaged_as_cdylib`, or the validated `codegen.consumer_api_style`
|
||||
# override. See the `let` above for the one override that is reachable
|
||||
# and why the other is refused there rather than at compile time.
|
||||
consumer_api_style = consumerApiStyle_;
|
||||
|
||||
# Privileged host services this module asks the host to grant it, from a
|
||||
# CLOSED set. Parsed and validated here, unlike the older decorative
|
||||
|
||||
+6
-1
@@ -31,13 +31,18 @@ let
|
||||
templateTests = import ./test-templates.nix { inherit assertEq assertBool assertHasAttr parseMetadata; builderRoot = ./..; };
|
||||
collectDepsTests = import ./test-collectAllModuleDeps.nix { inherit assertEq assertBool assertHasAttr common; };
|
||||
fixtureTests = import ./test-fixtures.nix { inherit assertEq assertBool assertHasAttr parseMetadata fixturesRoot; };
|
||||
# The consumer axis (codegen.consumer_api_style) and the gate on it. Its own
|
||||
# file rather than more cases in test-parse-metadata.nix: what it pins is a
|
||||
# safety boundary (which images may hold a LogosAPI-free consumer wrapper),
|
||||
# not a parsing default, and the reasoning belongs next to the cases.
|
||||
consumerApiStyleTests = import ./test-consumer-api-style.nix { inherit assertEq assertBool assertThrows parseMetadata; };
|
||||
modulePreConfigureTests = import ./test-module-pre-configure.nix {
|
||||
inherit lib assertBool assertThrows;
|
||||
modulePreConfigure = import ../lib/modulePreConfigure.nix { inherit lib; };
|
||||
};
|
||||
|
||||
# Collect all test results into a list of bools (all must be true)
|
||||
allTests = parseMetadataTests ++ commonTests ++ externalLibTests ++ templateTests ++ collectDepsTests ++ fixtureTests ++ modulePreConfigureTests;
|
||||
allTests = parseMetadataTests ++ commonTests ++ externalLibTests ++ templateTests ++ collectDepsTests ++ fixtureTests ++ modulePreConfigureTests ++ consumerApiStyleTests;
|
||||
|
||||
# Force evaluation of all tests
|
||||
allPassed = builtins.deepSeq allTests (builtins.length allTests);
|
||||
|
||||
@@ -0,0 +1,100 @@
|
||||
# The CONSUMER axis: `codegen.consumer_api_style`, and the gate on it.
|
||||
#
|
||||
# What is being pinned here is not "the key parses". It is the boundary the key
|
||||
# is allowed to cross, which is a SAFETY boundary rather than a typing one:
|
||||
#
|
||||
# A generated consumer wrapper that holds no LogosAPI (the lp wrappers, and
|
||||
# the Qt wrappers emitted under `--binding origin`) can only authenticate its
|
||||
# outbound calls if something else populates the TokenManager its lp client
|
||||
# reads. In a cdylib provider image that happens over the module-impl C ABI
|
||||
# (`logos_module_accept_token` -> `lp_token_save`). In a Qt PLUGIN image it
|
||||
# does not: the host writes tokens to the TokenManager in its OWN image, and
|
||||
# the only thing that mirrors them across is logos::qt::LpBridge::syncTokens,
|
||||
# installed exclusively by `forTarget(api, ...)`.
|
||||
#
|
||||
# So the same "no LogosAPI" wrapper is correct in one image and silently
|
||||
# unauthenticated in the other — calls come back as default values with no
|
||||
# error raised. `packaged_as_cdylib` is the predicate that separates them, and
|
||||
# these cases are the record of where it falls.
|
||||
#
|
||||
# Every default below must equal what logos-plugin-qt buildPlugin.nix derived
|
||||
# before this key existed, or every module in the tree rebuilds.
|
||||
{ assertEq, assertBool, assertThrows, parseMetadata }:
|
||||
|
||||
let
|
||||
parse = parseMetadata.parseModuleConfig;
|
||||
|
||||
mk = attrs: parse (builtins.toJSON ({ name = "m"; } // attrs));
|
||||
|
||||
# The four shapes, with no key set.
|
||||
legacyCore = mk { }; # handcrafted Qt plugin
|
||||
legacyUi = mk { type = "ui"; };
|
||||
universalCore = mk { interface = "universal"; type = "core"; }; # header-first cdylib
|
||||
universalUi = mk { interface = "universal"; type = "ui_qml"; }; # view backend, NOT a module
|
||||
cdylibCore = mk { interface = "cdylib"; type = "core"; };
|
||||
|
||||
# Overridden.
|
||||
cdylibQt = mk { interface = "cdylib"; type = "core";
|
||||
codegen = { consumer_api_style = "qt"; }; };
|
||||
universalQt = mk { interface = "universal"; type = "core";
|
||||
codegen = { consumer_api_style = "qt"; }; };
|
||||
cdylibLpExplicit = mk { interface = "cdylib"; type = "core";
|
||||
codegen = { consumer_api_style = "lp"; }; };
|
||||
legacyQtExplicit = mk { codegen = { consumer_api_style = "qt"; }; };
|
||||
|
||||
in [
|
||||
# ── packaged_as_cdylib: the predicate itself ───────────────────────────────
|
||||
# Same expression modulePreConfigure.autoCodegen branches on when it decides
|
||||
# to emit the module-impl C ABI export surface. If these two ever disagree, a
|
||||
# module gets an origin-bound wrapper in an image with no accept_token.
|
||||
(assertBool "legacy core is NOT cdylib-packaged" legacyCore.packaged_as_cdylib false)
|
||||
(assertBool "legacy ui is NOT cdylib-packaged" legacyUi.packaged_as_cdylib false)
|
||||
(assertBool "universal core IS cdylib-packaged" universalCore.packaged_as_cdylib true)
|
||||
(assertBool "universal ui_qml is NOT cdylib-packaged" universalUi.packaged_as_cdylib false)
|
||||
(assertBool "cdylib IS cdylib-packaged" cdylibCore.packaged_as_cdylib true)
|
||||
|
||||
# ── Defaults: byte-identity with the pre-key derivation ────────────────────
|
||||
(assertEq "legacy core defaults to qt" legacyCore.consumer_api_style "qt")
|
||||
(assertEq "legacy ui defaults to qt" legacyUi.consumer_api_style "qt")
|
||||
(assertEq "universal core defaults to lp" universalCore.consumer_api_style "lp")
|
||||
(assertEq "universal ui_qml defaults to qt" universalUi.consumer_api_style "qt")
|
||||
(assertEq "cdylib defaults to lp" cdylibCore.consumer_api_style "lp")
|
||||
|
||||
# ── The one override the key exists for ────────────────────────────────────
|
||||
# A cdylib provider consuming its dependencies through Qt-typed wrappers.
|
||||
# Its image has no LogosAPI and takes tokens over the C ABI, so the
|
||||
# origin-bound wrapper is correct there.
|
||||
(assertEq "cdylib may ask for the Qt consumer surface" cdylibQt.consumer_api_style "qt")
|
||||
(assertEq "universal core may ask for the Qt consumer surface" universalQt.consumer_api_style "qt")
|
||||
|
||||
# Restating a default is a no-op, not an error.
|
||||
(assertEq "cdylib may restate lp" cdylibLpExplicit.consumer_api_style "lp")
|
||||
(assertEq "legacy may restate qt" legacyQtExplicit.consumer_api_style "qt")
|
||||
|
||||
# ── THE GATE ───────────────────────────────────────────────────────────────
|
||||
# A Qt plugin image must not get LogosAPI-free consumer wrappers. Refused at
|
||||
# EVAL, naming `codegen.consumer_api_style`, because the alternatives are a
|
||||
# compile error deep inside generated code (the umbrella shapes disagree) or —
|
||||
# worse, where they happen to agree — a module that builds green and then
|
||||
# cannot authenticate a single outbound call.
|
||||
(assertThrows "a legacy Qt plugin cannot ask for the lp consumer surface"
|
||||
(mk { codegen = { consumer_api_style = "lp"; }; }).consumer_api_style)
|
||||
(assertThrows "a legacy ui plugin cannot ask for the lp consumer surface"
|
||||
(mk { type = "ui"; codegen = { consumer_api_style = "lp"; }; }).consumer_api_style)
|
||||
# The one that is easy to get wrong: `interface: "universal"` looks
|
||||
# cdylib-shaped, but a ui_qml backend is a Qt view object holding a LogosAPI.
|
||||
(assertThrows "a universal ui_qml backend cannot ask for the lp consumer surface"
|
||||
(mk { interface = "universal"; type = "ui_qml";
|
||||
codegen = { consumer_api_style = "lp"; }; }).consumer_api_style)
|
||||
|
||||
# ── Value validation ───────────────────────────────────────────────────────
|
||||
# An unrecognised value is refused rather than defaulted: defaulting a
|
||||
# misspelling back to the derived surface is how a module silently keeps the
|
||||
# binding its author was trying to change.
|
||||
(assertThrows "an unknown consumer_api_style is refused"
|
||||
(mk { interface = "cdylib"; codegen = { consumer_api_style = "Qt"; }; }).consumer_api_style)
|
||||
(assertThrows "the --binding value is not a consumer_api_style value"
|
||||
(mk { interface = "cdylib"; codegen = { consumer_api_style = "origin"; }; }).consumer_api_style)
|
||||
(assertThrows "a non-string consumer_api_style is refused"
|
||||
(mk { interface = "cdylib"; codegen = { consumer_api_style = true; }; }).consumer_api_style)
|
||||
]
|
||||
Reference in New Issue
Block a user