Skip to content

Commit 0bda0a6

Browse files
authored
Add support for importing Rust types from another crate Slint compilation (#9329)
To implement an external Slint library, the types and components implemented in the .slint files needs to be exposed through the Rust crate. A simple example example/app-library is added to demonstrate how to use this feature. CC #7060
1 parent b23a657 commit 0bda0a6

30 files changed

+766
-42
lines changed

Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,9 @@ members = [
6161
'tests/driver/rust',
6262
'tests/screenshots',
6363
'tests/manual/windowattributes',
64+
'tests/manual/module-builds/blogica',
65+
'tests/manual/module-builds/blogicb',
66+
'tests/manual/module-builds/app',
6467
'tools/compiler',
6568
'tools/docsnapper',
6669
'tools/figma_import',

api/rs/build/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ path = "lib.rs"
1919

2020
[features]
2121
default = []
22+
experimental-module-builds = ["i-slint-compiler/experimental-library-module"]
2223
sdf-fonts = ["i-slint-compiler/sdf-fonts"]
2324

2425
[dependencies]

api/rs/build/lib.rs

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,26 @@ impl CompilerConfiguration {
205205
Self { config }
206206
}
207207

208+
/// Configures the compiler to treat the Slint as part of a library.
209+
///
210+
/// Use this when the components and types of the Slint code need
211+
/// to be accessible from other modules.
212+
#[cfg(feature = "experimental-module-builds")]
213+
#[must_use]
214+
pub fn as_library(self, library_name: &str) -> Self {
215+
let mut config = self.config;
216+
config.library_name = Some(library_name.to_string());
217+
Self { config }
218+
}
219+
220+
/// Specify the Rust module to place the generated code in.
221+
#[cfg(feature = "experimental-module-builds")]
222+
#[must_use]
223+
pub fn rust_module(self, rust_module: &str) -> Self {
224+
let mut config = self.config;
225+
config.rust_module = Some(rust_module.to_string());
226+
Self { config }
227+
}
208228
/// Configures the compiler to use Signed Distance Field (SDF) encoding for fonts.
209229
///
210230
/// This flag only takes effect when `embed_resources` is set to [`EmbedResourcesKind::EmbedForSoftwareRenderer`],
@@ -429,6 +449,18 @@ pub fn compile_with_config(
429449
.with_extension("rs"),
430450
);
431451

452+
#[cfg(feature = "experimental-module-builds")]
453+
if let Some(library_name) = config.config.library_name.clone() {
454+
println!("cargo::metadata=SLINT_LIBRARY_NAME={}", library_name);
455+
println!(
456+
"cargo::metadata=SLINT_LIBRARY_PACKAGE={}",
457+
std::env::var("CARGO_PKG_NAME").ok().unwrap_or_default()
458+
);
459+
println!("cargo::metadata=SLINT_LIBRARY_SOURCE={}", path.display());
460+
if let Some(rust_module) = &config.config.rust_module {
461+
println!("cargo::metadata=SLINT_LIBRARY_MODULE={}", rust_module);
462+
}
463+
}
432464
let paths_dependencies =
433465
compile_with_output_path(path, absolute_rust_output_file_path.clone(), config)?;
434466

internal/compiler/Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,9 @@ sdf-fonts = ["dep:fdsm", "dep:fdsm-ttf-parser", "dep:nalgebra", "dep:rayon"]
3535
# Translation bundler
3636
bundle-translations = ["dep:polib"]
3737

38+
# Enable expermental library module support
39+
experimental-library-module = []
40+
3841
default = []
3942

4043
[dependencies]

internal/compiler/generator/rust.rs

Lines changed: 155 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ use crate::llr::{
2020
TypeResolutionContext as _,
2121
};
2222
use crate::object_tree::Document;
23+
use crate::typeloader::LibraryInfo;
2324
use crate::CompilerConfiguration;
2425
use itertools::Either;
2526
use lyon_path::geom::euclid::approxeq::ApproxEq;
@@ -160,6 +161,36 @@ pub fn generate(
160161
return super::rust_live_preview::generate(doc, compiler_config);
161162
}
162163

164+
let module_header = generate_module_header();
165+
let qualified_name_ident = |symbol: &SmolStr, library_info: &LibraryInfo| {
166+
let symbol = ident(symbol);
167+
let package = ident(&library_info.package);
168+
if let Some(module) = &library_info.module {
169+
let module = ident(module);
170+
quote!(#package :: #module :: #symbol)
171+
} else {
172+
quote!(#package :: #symbol)
173+
}
174+
};
175+
176+
let library_imports = {
177+
let doc_used_types = doc.used_types.borrow();
178+
doc_used_types
179+
.library_types_imports
180+
.iter()
181+
.map(|(symbol, library_info)| {
182+
let ident = qualified_name_ident(symbol, library_info);
183+
quote!(pub use #ident;)
184+
})
185+
.chain(doc_used_types.library_global_imports.iter().map(|(symbol, library_info)| {
186+
let ident = qualified_name_ident(symbol, library_info);
187+
let inner_symbol_name = smol_str::format_smolstr!("Inner{}", symbol);
188+
let inner_ident = qualified_name_ident(&inner_symbol_name, library_info);
189+
quote!(pub use #ident, #inner_ident;)
190+
}))
191+
.collect::<Vec<_>>()
192+
};
193+
163194
let (structs_and_enums_ids, inner_module) =
164195
generate_types(&doc.used_types.borrow().structs_and_enums);
165196

@@ -180,12 +211,22 @@ pub fn generate(
180211
let popup_menu =
181212
llr.popup_menu.as_ref().map(|p| generate_item_tree(&p.item_tree, &llr, None, None, true));
182213

183-
let globals = llr
214+
let mut global_exports = Vec::<TokenStream>::new();
215+
if let Some(library_name) = &compiler_config.library_name {
216+
// Building as a library, SharedGlobals needs to be exported
217+
let ident = format_ident!("{}SharedGlobals", library_name);
218+
global_exports.push(quote!(SharedGlobals as #ident));
219+
}
220+
let globals =
221+
llr.globals.iter_enumerated().filter(|(_, glob)| glob.must_generate()).map(
222+
|(idx, glob)| generate_global(idx, glob, &llr, compiler_config, &mut global_exports),
223+
);
224+
let library_globals_getters = llr
184225
.globals
185226
.iter_enumerated()
186-
.filter(|(_, glob)| glob.must_generate())
187-
.map(|(idx, glob)| generate_global(idx, glob, &llr));
188-
let shared_globals = generate_shared_globals(&llr, compiler_config);
227+
.filter(|(_, glob)| glob.from_library)
228+
.map(|(_idx, glob)| generate_global_getters(glob, &llr));
229+
let shared_globals = generate_shared_globals(&doc, &llr, compiler_config);
189230
let globals_ids = llr.globals.iter().filter(|glob| glob.exported).flat_map(|glob| {
190231
std::iter::once(ident(&glob.name)).chain(glob.aliases.iter().map(|x| ident(x)))
191232
});
@@ -207,8 +248,11 @@ pub fn generate(
207248

208249
Ok(quote! {
209250
mod #generated_mod {
251+
#module_header
252+
#(#library_imports)*
210253
#inner_module
211254
#(#globals)*
255+
#(#library_globals_getters)*
212256
#(#sub_compos)*
213257
#popup_menu
214258
#(#public_components)*
@@ -217,12 +261,25 @@ pub fn generate(
217261
#translations
218262
}
219263
#[allow(unused_imports)]
220-
pub use #generated_mod::{#(#compo_ids,)* #(#structs_and_enums_ids,)* #(#globals_ids,)* #(#named_exports,)*};
264+
pub use #generated_mod::{#(#compo_ids,)* #(#structs_and_enums_ids,)* #(#globals_ids,)* #(#named_exports,)* #(#global_exports,)*};
221265
#[allow(unused_imports)]
222266
pub use slint::{ComponentHandle as _, Global as _, ModelExt as _};
223267
})
224268
}
225269

270+
pub(super) fn generate_module_header() -> TokenStream {
271+
quote! {
272+
#![allow(non_snake_case, non_camel_case_types)]
273+
#![allow(unused_braces, unused_parens)]
274+
#![allow(clippy::all, clippy::pedantic, clippy::nursery)]
275+
#![allow(unknown_lints, if_let_rescope, tail_expr_drop_order)] // We don't have fancy Drop
276+
277+
use slint::private_unstable_api::re_exports as sp;
278+
#[allow(unused_imports)]
279+
use sp::{RepeatedItemTree as _, ModelExt as _, Model as _, Float as _};
280+
}
281+
}
282+
226283
/// Generate the struct and enums. Return a vector of names to import and a token stream with the inner module
227284
pub fn generate_types(used_types: &[Type]) -> (Vec<Ident>, TokenStream) {
228285
let (structs_and_enums_ids, structs_and_enum_def): (Vec<_>, Vec<_>) = used_types
@@ -247,14 +304,6 @@ pub fn generate_types(used_types: &[Type]) -> (Vec<Ident>, TokenStream) {
247304
);
248305

249306
let inner_module = quote! {
250-
#![allow(non_snake_case, non_camel_case_types)]
251-
#![allow(unused_braces, unused_parens)]
252-
#![allow(clippy::all, clippy::pedantic, clippy::nursery)]
253-
#![allow(unknown_lints, if_let_rescope, tail_expr_drop_order)] // We don't have fancy Drop
254-
255-
use slint::private_unstable_api::re_exports as sp;
256-
#[allow(unused_imports)]
257-
use sp::{RepeatedItemTree as _, ModelExt as _, Model as _, Float as _};
258307
#(#structs_and_enum_def)*
259308
const _THE_SAME_VERSION_MUST_BE_USED_FOR_THE_COMPILER_AND_THE_RUNTIME : slint::#version_check = slint::#version_check;
260309
};
@@ -361,6 +410,7 @@ fn generate_public_component(
361410
}
362411

363412
fn generate_shared_globals(
413+
doc: &Document,
364414
llr: &llr::CompilationUnit,
365415
compiler_config: &CompilerConfiguration,
366416
) -> TokenStream {
@@ -377,6 +427,15 @@ fn generate_shared_globals(
377427
.map(global_inner_name)
378428
.collect::<Vec<_>>();
379429

430+
let from_library_global_names = llr
431+
.globals
432+
.iter()
433+
.filter(|g| g.from_library)
434+
.map(|g| format_ident!("global_{}", ident(&g.name)))
435+
.collect::<Vec<_>>();
436+
437+
let from_library_global_types =
438+
llr.globals.iter().filter(|g| g.from_library).map(global_inner_name).collect::<Vec<_>>();
380439
let apply_constant_scale_factor = if !compiler_config.const_scale_factor.approx_eq(&1.0) {
381440
let factor = compiler_config.const_scale_factor as f32;
382441
Some(
@@ -386,18 +445,59 @@ fn generate_shared_globals(
386445
None
387446
};
388447

448+
let library_global_vars = llr
449+
.globals
450+
.iter()
451+
.filter(|g| g.from_library)
452+
.map(|g| {
453+
let library_info = doc.library_exports.get(g.name.as_str()).unwrap();
454+
let shared_gloabls_var_name =
455+
format_ident!("library_{}_shared_globals", library_info.name);
456+
let global_name = format_ident!("global_{}", ident(&g.name));
457+
quote!( #shared_gloabls_var_name.#global_name )
458+
})
459+
.collect::<Vec<_>>();
460+
let pub_token = if compiler_config.library_name.is_some() { quote!(pub) } else { quote!() };
461+
462+
let (library_shared_globals_names, library_shared_globals_types): (Vec<_>, Vec<_>) = doc
463+
.imports
464+
.iter()
465+
.filter_map(|import| import.library_info.clone())
466+
.map(|library_info| {
467+
let struct_name = format_ident!("{}SharedGlobals", library_info.name);
468+
let shared_gloabls_var_name =
469+
format_ident!("library_{}_shared_globals", library_info.name);
470+
let shared_globals_type_name = if let Some(module) = library_info.module {
471+
let package = ident(&library_info.package);
472+
let module = ident(&module);
473+
//(quote!(#shared_gloabls_var_name),quote!(let #shared_gloabls_var_name = #package::#module::#shared_globals_type_name::new(root_item_tree_weak.clone());))
474+
quote!(#package::#module::#struct_name)
475+
} else {
476+
let package = ident(&library_info.package);
477+
quote!(#package::#struct_name)
478+
};
479+
(quote!(#shared_gloabls_var_name), shared_globals_type_name)
480+
})
481+
.unzip();
482+
389483
quote! {
390-
struct SharedGlobals {
391-
#(#global_names : ::core::pin::Pin<sp::Rc<#global_types>>,)*
484+
#pub_token struct SharedGlobals {
485+
#(#pub_token #global_names : ::core::pin::Pin<sp::Rc<#global_types>>,)*
486+
#(#pub_token #from_library_global_names : ::core::pin::Pin<sp::Rc<#from_library_global_types>>,)*
392487
window_adapter : sp::OnceCell<sp::WindowAdapterRc>,
393488
root_item_tree_weak : sp::VWeak<sp::ItemTreeVTable>,
489+
#(#[allow(dead_code)]
490+
#library_shared_globals_names : sp::Rc<#library_shared_globals_types>,)*
394491
}
395492
impl SharedGlobals {
396-
fn new(root_item_tree_weak : sp::VWeak<sp::ItemTreeVTable>) -> sp::Rc<Self> {
493+
#pub_token fn new(root_item_tree_weak : sp::VWeak<sp::ItemTreeVTable>) -> sp::Rc<Self> {
494+
#(let #library_shared_globals_names = #library_shared_globals_types::new(root_item_tree_weak.clone());)*
397495
let _self = sp::Rc::new(Self {
398496
#(#global_names : #global_types::new(),)*
497+
#(#from_library_global_names : #library_global_vars.clone(),)*
399498
window_adapter : ::core::default::Default::default(),
400499
root_item_tree_weak,
500+
#(#library_shared_globals_names,)*
401501
});
402502
#(_self.#global_names.clone().init(&_self);)*
403503
_self
@@ -1340,6 +1440,8 @@ fn generate_global(
13401440
global_idx: llr::GlobalIdx,
13411441
global: &llr::GlobalComponent,
13421442
root: &llr::CompilationUnit,
1443+
compiler_config: &CompilerConfiguration,
1444+
global_exports: &mut Vec<TokenStream>,
13431445
) -> TokenStream {
13441446
let mut declared_property_vars = vec![];
13451447
let mut declared_property_types = vec![];
@@ -1442,6 +1544,13 @@ fn generate_global(
14421544
}
14431545
}));
14441546

1547+
let pub_token = if compiler_config.library_name.is_some() {
1548+
global_exports.push(quote! (#inner_component_id));
1549+
quote!(pub)
1550+
} else {
1551+
quote!()
1552+
};
1553+
14451554
let public_interface = global.exported.then(|| {
14461555
let property_and_callback_accessors = public_api(
14471556
&global.public_properties,
@@ -1450,26 +1559,17 @@ fn generate_global(
14501559
&ctx,
14511560
);
14521561
let aliases = global.aliases.iter().map(|name| ident(name));
1453-
let getters = root.public_components.iter().map(|c| {
1454-
let root_component_id = ident(&c.name);
1455-
quote! {
1456-
impl<'a> slint::Global<'a, #root_component_id> for #public_component_id<'a> {
1457-
fn get(component: &'a #root_component_id) -> Self {
1458-
Self(&component.0.globals.get().unwrap().#global_id)
1459-
}
1460-
}
1461-
}
1462-
});
1562+
let getters = generate_global_getters(global, root);
14631563

14641564
quote!(
14651565
#[allow(unused)]
1466-
pub struct #public_component_id<'a>(&'a ::core::pin::Pin<sp::Rc<#inner_component_id>>);
1566+
pub struct #public_component_id<'a>(#pub_token &'a ::core::pin::Pin<sp::Rc<#inner_component_id>>);
14671567

14681568
impl<'a> #public_component_id<'a> {
14691569
#property_and_callback_accessors
14701570
}
14711571
#(pub type #aliases<'a> = #public_component_id<'a>;)*
1472-
#(#getters)*
1572+
#getters
14731573
)
14741574
});
14751575

@@ -1478,10 +1578,10 @@ fn generate_global(
14781578
#[const_field_offset(sp::const_field_offset)]
14791579
#[repr(C)]
14801580
#[pin]
1481-
struct #inner_component_id {
1482-
#(#declared_property_vars: sp::Property<#declared_property_types>,)*
1483-
#(#declared_callbacks: sp::Callback<(#(#declared_callbacks_types,)*), #declared_callbacks_ret>,)*
1484-
#(#change_tracker_names : sp::ChangeTracker,)*
1581+
#pub_token struct #inner_component_id {
1582+
#(#pub_token #declared_property_vars: sp::Property<#declared_property_types>,)*
1583+
#(#pub_token #declared_callbacks: sp::Callback<(#(#declared_callbacks_types,)*), #declared_callbacks_ret>,)*
1584+
#(#pub_token #change_tracker_names : sp::ChangeTracker,)*
14851585
globals : sp::OnceCell<sp::Weak<SharedGlobals>>,
14861586
}
14871587

@@ -1504,6 +1604,29 @@ fn generate_global(
15041604
)
15051605
}
15061606

1607+
fn generate_global_getters(
1608+
global: &llr::GlobalComponent,
1609+
root: &llr::CompilationUnit,
1610+
) -> TokenStream {
1611+
let public_component_id = ident(&global.name);
1612+
let global_id = format_ident!("global_{}", public_component_id);
1613+
1614+
let getters = root.public_components.iter().map(|c| {
1615+
let root_component_id = ident(&c.name);
1616+
quote! {
1617+
impl<'a> slint::Global<'a, #root_component_id> for #public_component_id<'a> {
1618+
fn get(component: &'a #root_component_id) -> Self {
1619+
Self(&component.0.globals.get().unwrap().#global_id)
1620+
}
1621+
}
1622+
}
1623+
});
1624+
1625+
quote! (
1626+
#(#getters)*
1627+
)
1628+
}
1629+
15071630
fn generate_item_tree(
15081631
sub_tree: &llr::ItemTree,
15091632
root: &llr::CompilationUnit,

0 commit comments

Comments
 (0)