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