Skip to content

Commit 8d1f9f1

Browse files
authored
feat(scaffold): honor per-target core-dependency overrides in the scripting bindings (#164)
* feat(scaffold): target_dep_overrides for the scripting backends Extend target_dep_overrides (previously ffi/jni/dart/swift only) to the pyo3/napi/magnus/php/rustler backends, mirroring the FFI backend: when overrides are set, the core dependency moves out of [dependencies] into a cfg(not(...)) default block plus one [target.'cfg(<cfg>)'.dependencies] block per override. - shared render_core_dep_with_overrides helper in scaffold/mod.rs - target_dep_overrides config field on Python/Node/Ruby/Php/Elixir (+ schema) - wired all five scaffolds - unit tests for the renderer + an all-five-backends integration test Fixes #163. * fix(tests): add target_dep_overrides to integration-test config literals The new field was added to the src/scaffold/tests literals but the integration tests under tests/ also construct PythonConfig/NodeConfig as full struct literals; --all-targets compilation failed with E0063. cargo check --all-targets, clippy (CI flags), and both affected test binaries are green.
1 parent 82030f6 commit 8d1f9f1

20 files changed

Lines changed: 336 additions & 23 deletions

schemas/alef.schema.json

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2215,6 +2215,14 @@
22152215
"string",
22162216
"null"
22172217
]
2218+
},
2219+
"target_dep_overrides": {
2220+
"default": [],
2221+
"description": "Per-target overrides for the core-crate dependency emitted into the\ngenerated `Cargo.toml`. See [`super::FfiConfig::target_dep_overrides`].",
2222+
"items": {
2223+
"$ref": "#/$defs/FfiTargetDepOverride"
2224+
},
2225+
"type": "array"
22182226
}
22192227
},
22202228
"type": "object"
@@ -3793,6 +3801,14 @@
37933801
"null"
37943802
]
37953803
},
3804+
"target_dep_overrides": {
3805+
"default": [],
3806+
"description": "Per-target overrides for the core-crate dependency emitted into the\ngenerated `Cargo.toml`. See [`super::FfiConfig::target_dep_overrides`].",
3807+
"items": {
3808+
"$ref": "#/$defs/FfiTargetDepOverride"
3809+
},
3810+
"type": "array"
3811+
},
37963812
"tokio_util_features": {
37973813
"default": null,
37983814
"description": "Features for the auto-emitted `tokio-util` dependency that backs napi trait-bridge\ncancellation tokens. Defaults to `[\"rt\"]` because `tokio_util::sync::CancellationToken`\nis gated behind the `rt` feature in tokio-util 0.7+. Override when a consumer needs\nadditional tokio-util features (e.g. `[\"rt\", \"codec\"]`).",
@@ -4328,6 +4344,14 @@
43284344
],
43294345
"default": null,
43304346
"description": "Output directory for generated PHP facade / stubs (e.g., `packages/php/src/`)."
4347+
},
4348+
"target_dep_overrides": {
4349+
"default": [],
4350+
"description": "Per-target overrides for the core-crate dependency emitted into the\ngenerated `Cargo.toml`. See [`super::FfiConfig::target_dep_overrides`].",
4351+
"items": {
4352+
"$ref": "#/$defs/FfiTargetDepOverride"
4353+
},
4354+
"type": "array"
43314355
}
43324356
},
43334357
"type": "object"
@@ -4684,6 +4708,14 @@
46844708
"type": "null"
46854709
}
46864710
]
4711+
},
4712+
"target_dep_overrides": {
4713+
"default": [],
4714+
"description": "Per-target overrides for the core-crate dependency emitted into the\ngenerated `Cargo.toml`. Mirrors [`super::FfiConfig::target_dep_overrides`]:\nwhen non-empty, the core dependency is wrapped in\n`[target.'cfg(not(<any-cfg>))'.dependencies]` plus one\n`[target.'cfg(<cfg>)'.dependencies]` block per override, so a specific\ntarget can swap the core crate's feature set.",
4715+
"items": {
4716+
"$ref": "#/$defs/FfiTargetDepOverride"
4717+
},
4718+
"type": "array"
46874719
}
46884720
},
46894721
"type": "object"
@@ -5652,6 +5684,14 @@
56525684
"type": "null"
56535685
}
56545686
]
5687+
},
5688+
"target_dep_overrides": {
5689+
"default": [],
5690+
"description": "Per-target overrides for the core-crate dependency emitted into the\ngenerated `Cargo.toml`. See [`super::FfiConfig::target_dep_overrides`].",
5691+
"items": {
5692+
"$ref": "#/$defs/FfiTargetDepOverride"
5693+
},
5694+
"type": "array"
56555695
}
56565696
},
56575697
"type": "object"

src/core/config/languages/elixir.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ use serde::{Deserialize, Serialize};
33
use std::collections::HashMap;
44
use std::path::PathBuf;
55

6+
use super::FfiTargetDepOverride;
7+
68
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
79
pub struct ElixirConfig {
810
pub app_name: Option<String>,
@@ -55,4 +57,8 @@ pub struct ElixirConfig {
5557
/// `aarch64-apple-darwin, aarch64-unknown-linux-gnu, x86_64-unknown-linux-gnu, x86_64-pc-windows-gnu`.
5658
#[serde(default)]
5759
pub nif_targets: Vec<String>,
60+
/// Per-target overrides for the core-crate dependency emitted into the
61+
/// generated `Cargo.toml`. See [`super::FfiConfig::target_dep_overrides`].
62+
#[serde(default)]
63+
pub target_dep_overrides: Vec<FfiTargetDepOverride>,
5864
}

src/core/config/languages/node.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ use serde::{Deserialize, Serialize};
33
use std::collections::HashMap;
44
use std::path::PathBuf;
55

6+
use super::FfiTargetDepOverride;
7+
68
/// Configuration for a single capsule type entry in `NodeConfig::capsule_types`.
79
///
810
/// When set, the named Rust type is NOT emitted as a `#[napi]` opaque wrapper.
@@ -125,4 +127,8 @@ pub struct NodeConfig {
125127
/// Extra paths to append to default lint commands (format, check, typecheck).
126128
#[serde(default)]
127129
pub extra_lint_paths: Vec<String>,
130+
/// Per-target overrides for the core-crate dependency emitted into the
131+
/// generated `Cargo.toml`. See [`super::FfiConfig::target_dep_overrides`].
132+
#[serde(default)]
133+
pub target_dep_overrides: Vec<FfiTargetDepOverride>,
128134
}

src/core/config/languages/php.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ use serde::{Deserialize, Serialize};
33
use std::collections::HashMap;
44
use std::path::PathBuf;
55

6-
use super::StubsConfig;
6+
use super::{FfiTargetDepOverride, StubsConfig};
77

88
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
99
pub struct PhpConfig {
@@ -62,4 +62,8 @@ pub struct PhpConfig {
6262
/// Extra paths to append to default lint commands (format, check, typecheck).
6363
#[serde(default)]
6464
pub extra_lint_paths: Vec<String>,
65+
/// Per-target overrides for the core-crate dependency emitted into the
66+
/// generated `Cargo.toml`. See [`super::FfiConfig::target_dep_overrides`].
67+
#[serde(default)]
68+
pub target_dep_overrides: Vec<FfiTargetDepOverride>,
6569
}

src/core/config/languages/python.rs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ use serde::{Deserialize, Serialize};
33
use std::collections::HashMap;
44
use std::path::PathBuf;
55

6-
use super::StubsConfig;
6+
use super::{FfiTargetDepOverride, StubsConfig};
77

88
/// Configuration for a single capsule type entry in `PythonConfig::capsule_types`.
99
///
@@ -143,4 +143,12 @@ pub struct PythonConfig {
143143
/// `ExtractionResult` instead of `_rust.ExtractionResult`.
144144
#[serde(default)]
145145
pub reexported_types: Vec<String>,
146+
/// Per-target overrides for the core-crate dependency emitted into the
147+
/// generated `Cargo.toml`. Mirrors [`super::FfiConfig::target_dep_overrides`]:
148+
/// when non-empty, the core dependency is wrapped in
149+
/// `[target.'cfg(not(<any-cfg>))'.dependencies]` plus one
150+
/// `[target.'cfg(<cfg>)'.dependencies]` block per override, so a specific
151+
/// target can swap the core crate's feature set.
152+
#[serde(default)]
153+
pub target_dep_overrides: Vec<FfiTargetDepOverride>,
146154
}

src/core/config/languages/ruby.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ use serde::{Deserialize, Serialize};
33
use std::collections::HashMap;
44
use std::path::PathBuf;
55

6-
use super::StubsConfig;
6+
use super::{FfiTargetDepOverride, StubsConfig};
77

88
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
99
pub struct RubyConfig {
@@ -41,4 +41,8 @@ pub struct RubyConfig {
4141
/// Extra paths to append to default lint commands (format, check, typecheck).
4242
#[serde(default)]
4343
pub extra_lint_paths: Vec<String>,
44+
/// Per-target overrides for the core-crate dependency emitted into the
45+
/// generated `Cargo.toml`. See [`super::FfiConfig::target_dep_overrides`].
46+
#[serde(default)]
47+
pub target_dep_overrides: Vec<FfiTargetDepOverride>,
4448
}

src/scaffold/languages/elixir.rs

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -66,13 +66,24 @@ pub(crate) fn scaffold_elixir_cargo(
6666
// Collect all [dependencies] entries then sort alphabetically so the emitted
6767
// Cargo.toml is cargo-sort canonical without a post-processing step.
6868
let features_str = core_dep_features(config, Language::Elixir);
69+
let core_overrides = config
70+
.elixir
71+
.as_ref()
72+
.map(|c| c.target_dep_overrides.as_slice())
73+
.unwrap_or(&[]);
74+
let (core_dep_line, core_target_blocks) = crate::scaffold::render_core_dep_with_overrides(
75+
&config.name,
76+
&format!("../../../../crates/{core_crate_dir}"),
77+
&features_str,
78+
version,
79+
core_overrides,
80+
);
81+
let core_target_blocks_section = if core_target_blocks.is_empty() {
82+
String::new()
83+
} else {
84+
format!("\n{core_target_blocks}")
85+
};
6986
let mut dep_lines: Vec<String> = vec![
70-
crate::scaffold::render_core_dep(
71-
&config.name,
72-
&format!("../../../../crates/{core_crate_dir}"),
73-
&features_str,
74-
version,
75-
),
7687
format!("rustler = \"{}\"", tv::cargo::RUSTLER),
7788
"serde = { version = \"1\", features = [\"derive\"] }".to_owned(),
7889
"serde_json = \"1\"".to_owned(),
@@ -108,6 +119,9 @@ pub(crate) fn scaffold_elixir_cargo(
108119
dep_lines.push("alloc-no-stdlib = \"=2.0.4\"".to_owned());
109120
dep_lines.push("alloc-stdlib = \"=0.2.2\"".to_owned());
110121
dep_lines.push("brotli-decompressor = \"=5.0.1\"".to_owned());
122+
if !core_dep_line.is_empty() {
123+
dep_lines.push(core_dep_line);
124+
}
111125
dep_lines.sort();
112126
let deps_section = dep_lines.join("\n");
113127

@@ -232,14 +246,15 @@ name = "{nif_name}"
232246
crate-type = ["cdylib"]
233247
234248
{features_table}[dependencies]
235-
{deps_section}{check_cfg_block}"#,
249+
{deps_section}{core_target_blocks_section}{check_cfg_block}"#,
236250
pkg_header = pkg_header,
237251
machete_section = machete_section,
238252
nif_name = nif_name,
239253
lib_path_line = lib_path_line,
240254
features_table = features_table,
241255
check_cfg_block = check_cfg_block,
242256
deps_section = deps_section,
257+
core_target_blocks_section = core_target_blocks_section,
243258
);
244259

245260
Ok(vec![GeneratedFile {

src/scaffold/languages/node.rs

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -221,12 +221,23 @@ pub(crate) fn scaffold_node_cargo(
221221
// Build [dependencies] block alphabetically sorted to match cargo-sort.
222222
// Order: async-trait?, futures-util?, <core-crate>, napi,
223223
// napi-derive, serde, serde_json, + any extra deps.
224-
let core_dep = crate::scaffold::render_core_dep(
224+
let core_overrides = config
225+
.node
226+
.as_ref()
227+
.map(|c| c.target_dep_overrides.as_slice())
228+
.unwrap_or(&[]);
229+
let (core_dep, core_target_blocks) = crate::scaffold::render_core_dep_with_overrides(
225230
&config.name,
226231
&format!("../{core_crate_dir}"),
227232
&core_dep_features(config, Language::Node),
228233
version,
234+
core_overrides,
229235
);
236+
let core_target_blocks_section = if core_target_blocks.is_empty() {
237+
String::new()
238+
} else {
239+
format!("{core_target_blocks}\n")
240+
};
230241
let mut dep_entries: Vec<String> = vec![
231242
format!(
232243
"napi = {{ version = \"{napi}\", features = [{napi_features_str}] }}",
@@ -288,12 +299,13 @@ crate-type = ["cdylib"]
288299
{features_table}[dependencies]
289300
{dep_block}
290301
291-
[build-dependencies]
302+
{core_target_blocks_section}[build-dependencies]
292303
napi-build = "{napi_build}"
293304
294305
"#,
295306
pkg_header = pkg_header,
296307
dep_block = dep_block,
308+
core_target_blocks_section = core_target_blocks_section,
297309
features_table = features_table,
298310
machete_ignored_str = machete_ignored_str,
299311
napi_build = tv::cargo::NAPI_BUILD,

src/scaffold/languages/php.rs

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -75,12 +75,23 @@ pub(crate) fn scaffold_php_cargo(api: &ApiSurface, config: &ResolvedCrateConfig)
7575
// Build [dependencies] block alphabetically sorted to match cargo-sort.
7676
// Order: async-trait?, ext-php-rs, futures-util?, <core-crate>,
7777
// serde, serde_json, tokio.
78-
let core_dep_php = crate::scaffold::render_core_dep(
78+
let core_overrides = config
79+
.php
80+
.as_ref()
81+
.map(|c| c.target_dep_overrides.as_slice())
82+
.unwrap_or(&[]);
83+
let (core_dep_php, core_target_blocks) = crate::scaffold::render_core_dep_with_overrides(
7984
&config.name,
8085
&format!("../{core_crate_dir}"),
8186
&core_dep_features(config, Language::Php),
8287
version,
88+
core_overrides,
8389
);
90+
let core_target_blocks_section = if core_target_blocks.is_empty() {
91+
String::new()
92+
} else {
93+
format!("\n{core_target_blocks}")
94+
};
8495
let mut dep_entries: Vec<String> = vec![
8596
format!("ext-php-rs = \"{}\"", tv::cargo::EXT_PHP_RS),
8697
"serde = { version = \"1\", features = [\"derive\"] }".to_string(),
@@ -139,10 +150,11 @@ extension-module = []
139150
{cfg_forwarding}
140151
[dependencies]
141152
{dep_block}
142-
153+
{core_target_blocks_section}
143154
"#,
144155
pkg_header = pkg_header,
145156
dep_block = dep_block,
157+
core_target_blocks_section = core_target_blocks_section,
146158
machete_ignored_str = machete_ignored_str,
147159
cfg_forwarding = cfg_forwarding,
148160
);

src/scaffold/languages/python.rs

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -157,12 +157,25 @@ pub(crate) fn scaffold_python_cargo(
157157
.collect::<Vec<_>>()
158158
.join(", ");
159159
// Build [dependencies] block alphabetically sorted to match cargo-sort.
160-
let core_dep_py = crate::scaffold::render_core_dep(
160+
// When target_dep_overrides are configured, the core dep moves into
161+
// `[target.'cfg(...)'.dependencies]` blocks (core_dep_py is then empty).
162+
let core_overrides = config
163+
.python
164+
.as_ref()
165+
.map(|p| p.target_dep_overrides.as_slice())
166+
.unwrap_or(&[]);
167+
let (core_dep_py, core_target_blocks) = crate::scaffold::render_core_dep_with_overrides(
161168
&config.name,
162169
&format!("../{core_crate_dir}"),
163170
&core_dep_features(config, Language::Python),
164171
version,
172+
core_overrides,
165173
);
174+
let core_target_blocks_section = if core_target_blocks.is_empty() {
175+
String::new()
176+
} else {
177+
format!("\n{core_target_blocks}")
178+
};
166179
let mut dep_entries: Vec<String> = vec![
167180
format!("pyo3 = {{ version = \"{}\" }}", tv::cargo::PYO3),
168181
format!(
@@ -207,11 +220,12 @@ extension-module = ["pyo3/extension-module", "pyo3/abi3-py310"]
207220
208221
[dependencies]
209222
{dep_block}
210-
223+
{core_target_blocks_section}
211224
"#,
212225
pkg_header = pkg_header,
213226
module_name = module_name,
214227
dep_block = dep_block,
228+
core_target_blocks_section = core_target_blocks_section,
215229
machete_ignored_str = machete_ignored_str,
216230
);
217231

0 commit comments

Comments
 (0)