1// Copyright © SixtyFPS GmbH <info@slint.dev>
2// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-1.1 OR LicenseRef-Slint-commercial
3
4use std::io::{BufWriter, Write};
5use std::path::Path;
6
7fn main() -> std::io::Result<()> {
8 let mut generated_file = BufWriter::new(std::fs::File::create(
9 Path::new(&std::env::var_os("OUT_DIR").unwrap()).join("generated.rs"),
10 )?);
11
12 for testcase in test_driver_lib::collect_test_cases("cases")? {
13 println!("cargo:rerun-if-changed={}", testcase.absolute_path.display());
14 let mut module_name = testcase.identifier();
15 if module_name.starts_with(|c: char| !c.is_ascii_alphabetic()) {
16 module_name.insert(0, '_');
17 }
18 if let Some(style) = testcase.requested_style {
19 module_name.push('_');
20 module_name.push_str(style);
21 }
22 writeln!(generated_file, "#[path=\"{0}.rs\"] mod r#{0};", module_name)?;
23 let source = std::fs::read_to_string(&testcase.absolute_path)?;
24 let ignored = testcase.is_ignored("rust");
25
26 let mut output = BufWriter::new(std::fs::File::create(
27 Path::new(&std::env::var_os("OUT_DIR").unwrap()).join(format!("{}.rs", module_name)),
28 )?);
29
30 #[cfg(not(feature = "build-time"))]
31 if !generate_macro(&source, &mut output, testcase)? {
32 continue;
33 }
34 #[cfg(feature = "build-time")]
35 generate_source(&source, &mut output, testcase)?;
36
37 for (i, x) in test_driver_lib::extract_test_functions(&source)
38 .filter(|x| x.language_id == "rust")
39 .enumerate()
40 {
41 write!(
42 output,
43 r"
44#[test] {} fn t_{}() -> std::result::Result<(), std::boxed::Box<dyn std::error::Error>> {{
45 use i_slint_backend_testing as slint_testing;
46 slint_testing::init();
47 {}
48 Ok(())
49}}",
50 if ignored { "#[ignore]" } else { "" },
51 i,
52 x.source.replace('\n', "\n ")
53 )?;
54 }
55 }
56
57 // By default resources are embedded. The WASM example builds provide test coverage for that. This switch
58 // provides test coverage for the non-embedding case, compiling tests without embedding the images.
59 println!("cargo:rustc-env=SLINT_EMBED_RESOURCES=false");
60
61 //Make sure to use a consistent style
62 println!("cargo:rustc-env=SLINT_STYLE=fluent");
63 println!("cargo:rustc-env=SLINT_ENABLE_EXPERIMENTAL_FEATURES=1");
64 Ok(())
65}
66
67#[cfg(not(feature = "build-time"))]
68fn generate_macro(
69 source: &str,
70 output: &mut dyn Write,
71 testcase: test_driver_lib::TestCase,
72) -> Result<bool, std::io::Error> {
73 if source.contains("\\{") {
74 // Unfortunately, \{ is not valid in a rust string so it cannot be used in a slint! macro
75 output.write_all(b"#[test] #[ignore] fn ignored_because_string_template() {{}}")?;
76 return Ok(false);
77 }
78 // to silence all the warnings in .slint files that would be turned into errors
79 output.write_all(b"#![allow(deprecated)]")?;
80 let include_paths = test_driver_lib::extract_include_paths(source);
81 let library_paths = test_driver_lib::extract_library_paths(source);
82 output.write_all(b"slint::slint!{")?;
83 for path in include_paths {
84 let mut abs_path = testcase.absolute_path.clone();
85 abs_path.pop();
86 abs_path.push(path);
87
88 output.write_all(b"#[include_path=r#\"")?;
89 output.write_all(abs_path.to_string_lossy().as_bytes())?;
90 output.write_all(b"\"#]\n")?;
91
92 println!("cargo:rerun-if-changed={}", abs_path.to_string_lossy());
93 }
94 for (lib, path) in library_paths {
95 let mut abs_path = testcase.absolute_path.clone();
96 abs_path.pop();
97 abs_path.push(path);
98
99 output.write_all(b"#[library_path(")?;
100 output.write_all(lib.as_bytes())?;
101 output.write_all(b")=r#\"")?;
102 output.write_all(abs_path.to_string_lossy().as_bytes())?;
103 output.write_all(b"\"#]\n")?;
104
105 println!("cargo:rerun-if-changed={}", abs_path.to_string_lossy());
106 }
107
108 if let Some(style) = testcase.requested_style {
109 output.write_all(b"#[style=\"")?;
110 output.write_all(style.as_bytes())?;
111 output.write_all(b"\"#]\n")?;
112 }
113
114 let mut abs_path = testcase.absolute_path;
115 abs_path.pop();
116 output.write_all(b"#[include_path=r#\"")?;
117 output.write_all(abs_path.to_string_lossy().as_bytes())?;
118 output.write_all(b"\"#]\n")?;
119 output.write_all(source.as_bytes())?;
120 output.write_all(b"}\n")?;
121 Ok(true)
122}
123
124#[cfg(feature = "build-time")]
125fn generate_source(
126 source: &str,
127 output: &mut impl Write,
128 testcase: test_driver_lib::TestCase,
129) -> Result<(), std::io::Error> {
130 use i_slint_compiler::{diagnostics::BuildDiagnostics, *};
131
132 let include_paths = test_driver_lib::extract_include_paths(source)
133 .map(std::path::PathBuf::from)
134 .collect::<Vec<_>>();
135 let library_paths = test_driver_lib::extract_library_paths(source)
136 .map(|(k, v)| (k.to_string(), std::path::PathBuf::from(v)))
137 .collect::<std::collections::HashMap<_, _>>();
138
139 let mut diag = BuildDiagnostics::default();
140 let syntax_node =
141 parser::parse(source.to_owned(), Some(&testcase.absolute_path), None, &mut diag);
142 let mut compiler_config = CompilerConfiguration::new(generator::OutputFormat::Rust);
143 compiler_config.enable_component_containers = true;
144 compiler_config.include_paths = include_paths;
145 compiler_config.library_paths = library_paths;
146 compiler_config.style = Some(testcase.requested_style.unwrap_or("fluent").to_string());
147 let (root_component, diag, _) =
148 spin_on::spin_on(compile_syntax_node(syntax_node, diag, compiler_config));
149
150 if diag.has_error() {
151 diag.print_warnings_and_exit_on_error();
152 return Err(std::io::Error::new(
153 std::io::ErrorKind::Other,
154 format!("build error in {:?}", testcase.absolute_path),
155 ));
156 } else {
157 diag.print();
158 }
159
160 generator::generate(generator::OutputFormat::Rust, output, &root_component)?;
161 Ok(())
162}
163