Skip to main content

slint_build/
lib.rs

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
4/*!
5This crate serves as a companion crate of the slint crate.
6It is meant to allow you to compile the `.slint` files from your `build.rs` script.
7
8The main entry point of this crate is the [`compile()`] function
9
10The generated code must be included in your crate by using the `slint::include_modules!()` macro.
11
12## Example
13
14In your Cargo.toml:
15
16```toml
17[package]
18...
19build = "build.rs"
20
21[dependencies]
22slint = "1.16.0"
23...
24
25[build-dependencies]
26slint-build = "1.16.0"
27```
28
29In the `build.rs` file:
30
31```ignore
32fn main() {
33    slint_build::compile("ui/hello.slint").unwrap();
34}
35```
36
37Then in your main file
38
39```ignore
40slint::include_modules!();
41fn main() {
42    HelloWorld::new().run();
43}
44```
45*/
46#![doc(html_logo_url = "https://slint.dev/logo/slint-logo-square-light.svg")]
47#![warn(missing_docs)]
48
49#[cfg(not(feature = "compat-1-18"))]
50compile_error!(
51    "The feature `compat-1-18` must be enabled to ensure \
52    forward compatibility with future version of this crate"
53);
54
55use std::collections::HashMap;
56use std::env;
57use std::io::{BufWriter, Write};
58use std::path::Path;
59
60use i_slint_compiler::diagnostics::BuildDiagnostics;
61
62/// Argument of [`CompilerConfiguration::with_default_translation_context()`]
63///
64pub use i_slint_compiler::DefaultTranslationContext;
65
66/// The structure for configuring aspects of the compilation of `.slint` markup files to Rust.
67#[derive(Clone)]
68pub struct CompilerConfiguration {
69    config: i_slint_compiler::CompilerConfiguration,
70}
71
72/// How should the Slint compiler embed images and fonts
73///
74/// Parameter of [`CompilerConfiguration::embed_resources()`]
75#[derive(Clone, PartialEq)]
76pub enum EmbedResourcesKind {
77    /// Resources are loaded from their absolute path at run-time.
78    ///
79    /// Only useful for debugging, since the files must still be present at the same path on the
80    /// machine running the application.
81    AsAbsolutePath,
82    /// The files referenced from .slint files are embedded in the binary as-is (for example
83    /// a PNG stays compressed), and decoded at run-time.
84    EmbedFiles,
85    #[cfg(feature = "renderer-software")]
86    /// Images and fonts are pre-processed at compile time and embedded as uncompressed pixel
87    /// data, ready to be drawn by the software renderer without any decoding at run-time.
88    ///
89    /// Useful for MCUs with no file system and little RAM.
90    /// Only the Slint software renderer can use these resources; Skia and FemtoVG can't.
91    EmbedForSoftwareRenderer,
92}
93
94impl Default for CompilerConfiguration {
95    fn default() -> Self {
96        Self {
97            config: i_slint_compiler::CompilerConfiguration::new(
98                i_slint_compiler::generator::OutputFormat::Rust,
99            ),
100        }
101    }
102}
103
104impl CompilerConfiguration {
105    /// Creates a new default configuration.
106    pub fn new() -> Self {
107        Self::default()
108    }
109
110    /// Create a new configuration that includes sets the include paths used for looking up
111    /// `.slint` imports to the specified vector of paths.
112    #[must_use]
113    pub fn with_include_paths(self, include_paths: Vec<std::path::PathBuf>) -> Self {
114        let mut config = self.config;
115        config.include_paths = include_paths;
116        Self { config }
117    }
118
119    /// Create a new configuration that sets the library paths used for looking up
120    /// `@library` imports to the specified map of paths.
121    ///
122    /// Each library path can either be a path to a `.slint` file or a directory.
123    /// If it's a file, the library is imported by its name prefixed by `@` (e.g.
124    /// `@example`). The specified file is the only entry-point for the library
125    /// and other files from the library won't be accessible from the outside.
126    /// If it's a directory, a specific file in that directory must be specified
127    /// when importing the library (e.g. `@example/widgets.slint`). This allows
128    /// exposing multiple entry-points for a single library.
129    ///
130    /// Compile `ui/main.slint` and specify an "example" library path:
131    /// ```rust,no_run
132    /// let manifest_dir = std::path::PathBuf::from(std::env::var_os("CARGO_MANIFEST_DIR").unwrap());
133    /// let library_paths = std::collections::HashMap::from([(
134    ///     "example".to_string(),
135    ///     manifest_dir.join("third_party/example/ui/lib.slint"),
136    /// )]);
137    /// let config = slint_build::CompilerConfiguration::new().with_library_paths(library_paths);
138    /// slint_build::compile_with_config("ui/main.slint", config).unwrap();
139    /// ```
140    ///
141    /// Import the "example" library in `ui/main.slint`:
142    /// ```slint,ignore
143    /// import { Example } from "@example";
144    /// ```
145    #[must_use]
146    pub fn with_library_paths(self, library_paths: HashMap<String, std::path::PathBuf>) -> Self {
147        let mut config = self.config;
148        config.library_paths = library_paths;
149        Self { config }
150    }
151
152    /// Create a new configuration that selects the style to be used for widgets.
153    #[must_use]
154    pub fn with_style(self, style: String) -> Self {
155        let mut config = self.config;
156        config.style = Some(style);
157        Self { config }
158    }
159
160    /// Selects how the resources such as images and font are processed.
161    ///
162    /// See [`EmbedResourcesKind`]
163    #[must_use]
164    pub fn embed_resources(self, kind: EmbedResourcesKind) -> Self {
165        let mut config = self.config;
166        config.embed_resources = match kind {
167            EmbedResourcesKind::AsAbsolutePath => {
168                i_slint_compiler::EmbedResourcesKind::OnlyBuiltinResources
169            }
170            EmbedResourcesKind::EmbedFiles => {
171                i_slint_compiler::EmbedResourcesKind::EmbedAllResources
172            }
173            #[cfg(feature = "renderer-software")]
174            EmbedResourcesKind::EmbedForSoftwareRenderer => {
175                i_slint_compiler::EmbedResourcesKind::EmbedTextures
176            }
177        };
178        Self { config }
179    }
180
181    /// Sets the scale factor to be applied to all `px` to `phx` conversions
182    /// as constant value. This is only intended for MCU environments. Use
183    /// in combination with [`Self::embed_resources`] to pre-scale images and glyphs
184    /// accordingly.
185    ///
186    /// If this is set, changing the scale factor at runtime will not have any effect.
187    #[must_use]
188    pub fn with_scale_factor(mut self, factor: f32) -> Self {
189        self.config.const_scale_factor = Some(factor);
190        self
191    }
192
193    /// Configures the compiler to bundle translations when compiling Slint code.
194    ///
195    /// It expects the path to be the root directory of the translation files.
196    ///
197    /// If given a relative path, it will be resolved relative to `$CARGO_MANIFEST_DIR`.
198    ///
199    /// The translation files should be in the gettext `.po` format and follow this pattern:
200    /// `<path>/<lang>/LC_MESSAGES/<crate>.po`
201    #[must_use]
202    pub fn with_bundled_translations(
203        self,
204        path: impl Into<std::path::PathBuf>,
205    ) -> CompilerConfiguration {
206        let mut config = self.config;
207        config.translation_path_bundle = Some(path.into());
208        Self { config }
209    }
210
211    /// Unless explicitly specified with the `@tr("context" => ...)`, the default translation context is the component name.
212    /// Use this option with [`DefaultTranslationContext::None`] to disable the default translation context.
213    ///
214    /// The translation file must also not have context
215    /// (`--no-default-translation-context` argument of `slint-tr-extractor`)
216    #[must_use]
217    pub fn with_default_translation_context(
218        mut self,
219        default_translation_context: DefaultTranslationContext,
220    ) -> Self {
221        self.config.default_translation_context = default_translation_context;
222        self
223    }
224
225    /// Configures the compiler to emit additional debug info when compiling Slint code.
226    ///
227    /// This is the equivalent to setting `SLINT_EMIT_DEBUG_INFO=1` and using the `slint!()` macro
228    /// and is primarily used by `i-slint-backend-testing`.
229    #[doc(hidden)]
230    #[must_use]
231    pub fn with_debug_info(self, enable: bool) -> Self {
232        let mut config = self.config;
233        config.debug_info = enable;
234        Self { config }
235    }
236
237    /// Configures the compiler to treat the Slint as part of a library.
238    ///
239    /// Use this when the components and types of the Slint code need
240    /// to be accessible from other modules.
241    ///
242    /// **Note**: This feature is experimental and may change or be removed in the future.
243    #[cfg(feature = "experimental-module-builds")]
244    #[must_use]
245    pub fn as_library(self, library_name: &str) -> Self {
246        let mut config = self.config;
247        config.library_name = Some(library_name.to_string());
248        Self { config }
249    }
250
251    /// Specify the Rust module to place the generated code in.
252    ///
253    /// **Note**: This feature is experimental and may change or be removed in the future.
254    #[cfg(feature = "experimental-module-builds")]
255    #[must_use]
256    pub fn rust_module(self, rust_module: &str) -> Self {
257        let mut config = self.config;
258        config.rust_module = Some(rust_module.to_string());
259        Self { config }
260    }
261    /// Configures the compiler to use Signed Distance Field (SDF) encoding for fonts.
262    ///
263    /// This flag only takes effect when `embed_resources` is set to [`EmbedResourcesKind::EmbedForSoftwareRenderer`],
264    /// and requires the `sdf-fonts` cargo feature to be enabled.
265    ///
266    /// [SDF](https://en.wikipedia.org/wiki/Signed_distance_function) reduces the binary size by
267    /// using an alternative representation for fonts, trading off some rendering quality
268    /// for a smaller binary footprint.
269    /// Rendering is slower and may result in slightly inferior visual output.
270    /// Use this on systems with limited flash memory.
271    #[cfg(feature = "sdf-fonts")]
272    #[must_use]
273    pub fn with_sdf_fonts(self, enable: bool) -> Self {
274        let mut config = self.config;
275        config.use_sdf_fonts = enable;
276        Self { config }
277    }
278
279    /// Converts any relative include_paths or library_paths to absolute paths relative to the manifest_dir.
280    #[must_use]
281    fn with_absolute_paths(self, manifest_dir: &std::path::Path) -> Self {
282        let mut config = self.config;
283
284        let to_absolute_path = |path: &mut std::path::PathBuf| {
285            if path.is_relative() {
286                *path = manifest_dir.join(&path);
287            }
288        };
289
290        for path in config.library_paths.values_mut() {
291            to_absolute_path(path);
292        }
293
294        for path in config.include_paths.iter_mut() {
295            to_absolute_path(path);
296        }
297
298        Self { config }
299    }
300}
301
302/// Error returned by the `compile` function
303#[derive(derive_more::Error, derive_more::Display, Debug)]
304#[non_exhaustive]
305pub enum CompileError {
306    /// Cannot read environment variable CARGO_MANIFEST_DIR or OUT_DIR. The build script need to be run via cargo.
307    #[display(
308        "Cannot read environment variable CARGO_MANIFEST_DIR or OUT_DIR. The build script need to be run via cargo."
309    )]
310    NotRunViaCargo,
311    /// Parse error. The error are printed in the stderr, and also are in the vector
312    #[display("{_0:?}")]
313    CompileError(#[error(not(source))] Vec<String>),
314    /// Cannot write the generated file
315    #[display("Cannot write the generated file: {_0}")]
316    SaveError(std::io::Error),
317}
318
319struct CodeFormatter<Sink> {
320    indentation: usize,
321    /// We are currently in a string
322    in_string: bool,
323    /// number of bytes after the last `'`, 0 if there was none
324    in_char: usize,
325    /// In string or char, and the previous character was `\\`
326    escaped: bool,
327    sink: Sink,
328}
329
330impl<Sink> CodeFormatter<Sink> {
331    pub fn new(sink: Sink) -> Self {
332        Self { indentation: 0, in_string: false, in_char: 0, escaped: false, sink }
333    }
334}
335
336impl<Sink: Write> Write for CodeFormatter<Sink> {
337    fn write(&mut self, mut s: &[u8]) -> std::io::Result<usize> {
338        let len = s.len();
339        while let Some(idx) = s.iter().position(|c| match c {
340            b'{' if !self.in_string && self.in_char == 0 => {
341                self.indentation += 1;
342                true
343            }
344            b'}' if !self.in_string && self.in_char == 0 => {
345                self.indentation -= 1;
346                true
347            }
348            b';' if !self.in_string && self.in_char == 0 => true,
349            b'"' if !self.in_string && self.in_char == 0 => {
350                self.in_string = true;
351                self.escaped = false;
352                false
353            }
354            b'"' if self.in_string && !self.escaped => {
355                self.in_string = false;
356                false
357            }
358            b'\'' if !self.in_string && self.in_char == 0 => {
359                self.in_char = 1;
360                self.escaped = false;
361                false
362            }
363            b'\'' if !self.in_string && self.in_char > 0 && !self.escaped => {
364                self.in_char = 0;
365                false
366            }
367            b' ' | b'>' if self.in_char > 2 && !self.escaped => {
368                // probably a lifetime
369                self.in_char = 0;
370                false
371            }
372            b'\\' if (self.in_string || self.in_char > 0) && !self.escaped => {
373                self.escaped = true;
374                // no need to increment in_char since \ isn't a single character
375                false
376            }
377            _ if self.in_char > 0 => {
378                self.in_char += 1;
379                self.escaped = false;
380                false
381            }
382            _ => {
383                self.escaped = false;
384                false
385            }
386        }) {
387            let idx = idx + 1;
388            self.sink.write_all(&s[..idx])?;
389            self.sink.write_all(b"\n")?;
390            for _ in 0..self.indentation {
391                self.sink.write_all(b"    ")?;
392            }
393            s = &s[idx..];
394        }
395        self.sink.write_all(s)?;
396        Ok(len)
397    }
398    fn flush(&mut self) -> std::io::Result<()> {
399        self.sink.flush()
400    }
401}
402
403#[test]
404fn formatter_test() {
405    fn format_code(code: &str) -> String {
406        let mut res = Vec::new();
407        let mut formatter = CodeFormatter::new(&mut res);
408        formatter.write_all(code.as_bytes()).unwrap();
409        String::from_utf8(res).unwrap()
410    }
411
412    assert_eq!(
413        format_code("fn main() { if ';' == '}' { return \";\"; } else { panic!() } }"),
414        r#"fn main() {
415     if ';' == '}' {
416         return ";";
417         }
418     else {
419         panic!() }
420     }
421"#
422    );
423
424    assert_eq!(
425        format_code(r#"fn xx<'lt>(foo: &'lt str) { println!("{}", '\u{f700}'); return Ok(()); }"#),
426        r#"fn xx<'lt>(foo: &'lt str) {
427     println!("{}", '\u{f700}');
428     return Ok(());
429     }
430"#
431    );
432
433    assert_eq!(
434        format_code(r#"fn main() { ""; "'"; "\""; "{}"; "\\"; "\\\""; }"#),
435        r#"fn main() {
436     "";
437     "'";
438     "\"";
439     "{}";
440     "\\";
441     "\\\"";
442     }
443"#
444    );
445
446    assert_eq!(
447        format_code(r#"fn main() { '"'; '\''; '{'; '}'; '\\'; }"#),
448        r#"fn main() {
449     '"';
450     '\'';
451     '{';
452     '}';
453     '\\';
454     }
455"#
456    );
457}
458
459/// Compile the `.slint` file and generate rust code for it.
460///
461/// The generated code code will be created in the directory specified by
462/// the `OUT` environment variable as it is expected for build script.
463///
464/// The following line need to be added within your crate in order to include
465/// the generated code.
466/// ```ignore
467/// slint::include_modules!();
468/// ```
469///
470/// The path is relative to the `CARGO_MANIFEST_DIR`.
471///
472/// In case of compilation error, the errors are shown in `stderr`, the error
473/// are also returned in the [`CompileError`] enum. You must `unwrap` the returned
474/// result to make sure that cargo make the compilation fail in case there were
475/// errors when generating the code.
476///
477/// Please check out the documentation of the `slint` crate for more information
478/// about how to use the generated code.
479///
480/// This function can only be called within a build script run by cargo.
481///
482/// See also [`compile_with_config()`] if you want to specify a configuration.
483pub fn compile(path: impl AsRef<std::path::Path>) -> Result<(), CompileError> {
484    compile_with_config(path, CompilerConfiguration::default())
485}
486
487/// Same as [`compile`], but allow to specify a configuration.
488///
489/// Compile `ui/hello.slint` and select the "material" style:
490/// ```rust,no_run
491/// let config =
492///     slint_build::CompilerConfiguration::new()
493///     .with_style("material".into());
494/// slint_build::compile_with_config("ui/hello.slint", config).unwrap();
495/// ```
496pub fn compile_with_config(
497    relative_slint_file_path: impl AsRef<std::path::Path>,
498    config: CompilerConfiguration,
499) -> Result<(), CompileError> {
500    let manifest_path = std::path::PathBuf::from(
501        env::var_os("CARGO_MANIFEST_DIR").ok_or(CompileError::NotRunViaCargo)?,
502    );
503    let config = config.with_absolute_paths(&manifest_path);
504
505    let path = manifest_path.join(relative_slint_file_path.as_ref());
506
507    let absolute_rust_output_file_path =
508        Path::new(&env::var_os("OUT_DIR").ok_or(CompileError::NotRunViaCargo)?).join(
509            path.file_stem()
510                .map(Path::new)
511                .unwrap_or_else(|| Path::new("slint_out"))
512                .with_extension("rs"),
513        );
514
515    #[cfg(feature = "experimental-module-builds")]
516    if let Some(library_name) = config.config.library_name.clone() {
517        println!("cargo::metadata=SLINT_LIBRARY_NAME={}", library_name);
518        println!(
519            "cargo::metadata=SLINT_LIBRARY_PACKAGE={}",
520            std::env::var("CARGO_PKG_NAME").ok().unwrap_or_default()
521        );
522        println!("cargo::metadata=SLINT_LIBRARY_SOURCE={}", path.display());
523        if let Some(rust_module) = &config.config.rust_module {
524            println!("cargo::metadata=SLINT_LIBRARY_MODULE={}", rust_module);
525        }
526    }
527    let paths_dependencies =
528        compile_with_output_path(path, absolute_rust_output_file_path.clone(), config)?;
529
530    for path_dependency in paths_dependencies {
531        println!("cargo:rerun-if-changed={}", path_dependency.display());
532    }
533
534    println!("cargo:rerun-if-env-changed=SLINT_STYLE");
535    println!("cargo:rerun-if-env-changed=SLINT_FONT_SIZES");
536    println!("cargo:rerun-if-env-changed=SLINT_SCALE_FACTOR");
537    println!("cargo:rerun-if-env-changed=SLINT_ASSET_SECTION");
538    println!("cargo:rerun-if-env-changed=SLINT_EMBED_RESOURCES");
539    println!("cargo:rerun-if-env-changed=SLINT_EMIT_DEBUG_INFO");
540    println!("cargo:rerun-if-env-changed=SLINT_LIVE_PREVIEW");
541
542    println!(
543        "cargo:rustc-env=SLINT_INCLUDE_GENERATED={}",
544        absolute_rust_output_file_path.display()
545    );
546
547    Ok(())
548}
549
550/// Similar to [`compile_with_config`], but meant to be used independently of cargo.
551///
552/// Will compile the input file and write the result in the given output file.
553///
554/// Both input_slint_file_path and output_rust_file_path should be absolute paths.
555///
556/// Doesn't print any cargo messages.
557///
558/// Returns a list of all input files that were used to generate the output file. (dependencies)
559pub fn compile_with_output_path(
560    input_slint_file_path: impl AsRef<std::path::Path>,
561    output_rust_file_path: impl AsRef<std::path::Path>,
562    config: CompilerConfiguration,
563) -> Result<Vec<std::path::PathBuf>, CompileError> {
564    let mut diag = BuildDiagnostics::default();
565    let syntax_node = i_slint_compiler::parser::parse_file(&input_slint_file_path, &mut diag);
566
567    if diag.has_errors() {
568        let vec = diag.to_string_vec();
569        diag.print();
570        return Err(CompileError::CompileError(vec));
571    }
572
573    let mut compiler_config = config.config;
574    compiler_config.translation_domain = std::env::var("CARGO_PKG_NAME").ok();
575
576    let syntax_node = syntax_node.expect("diags contained no compilation errors");
577
578    // 'spin_on' is ok here because the compiler in single threaded and does not block if there is no blocking future
579    let (doc, diag, loader) =
580        spin_on::spin_on(i_slint_compiler::compile_syntax_node(syntax_node, diag, compiler_config));
581
582    if diag.has_errors()
583        || (!diag.is_empty() && std::env::var("SLINT_COMPILER_DENY_WARNINGS").is_ok())
584    {
585        let vec = diag.to_string_vec();
586        diag.print();
587        return Err(CompileError::CompileError(vec));
588    }
589
590    let output_file =
591        std::fs::File::create(&output_rust_file_path).map_err(CompileError::SaveError)?;
592    let mut code_formatter = CodeFormatter::new(BufWriter::new(output_file));
593    let generated = i_slint_compiler::generator::rust::generate(&doc, &loader.compiler_config)
594        .map_err(|e| CompileError::CompileError(vec![e.to_string()]))?;
595
596    let mut dependencies: Vec<std::path::PathBuf> = Vec::new();
597
598    for x in &diag.all_loaded_files {
599        if x.is_absolute() {
600            dependencies.push(x.clone());
601        }
602    }
603
604    // print warnings
605    diag.diagnostics_as_string().lines().for_each(|w| {
606        if !w.is_empty() {
607            println!("cargo:warning={}", w.strip_prefix("warning: ").unwrap_or(w))
608        }
609    });
610
611    write!(code_formatter, "{generated}").map_err(CompileError::SaveError)?;
612    dependencies.push(input_slint_file_path.as_ref().to_path_buf());
613
614    for er in doc.embedded_file_resources.borrow().iter() {
615        if let Some(resource) = er.path.as_deref()
616            && !resource.starts_with("builtin:")
617        {
618            dependencies.push(Path::new(resource).to_path_buf());
619        }
620    }
621
622    code_formatter.sink.flush().map_err(CompileError::SaveError)?;
623
624    Ok(dependencies)
625}
626
627/// This function is for use the application's build script, in order to print any device specific
628/// build flags reported by the backend
629pub fn print_rustc_flags() -> std::io::Result<()> {
630    if let Some(board_config_path) =
631        std::env::var_os("DEP_MCU_BOARD_SUPPORT_BOARD_CONFIG_PATH").map(std::path::PathBuf::from)
632    {
633        let config = std::fs::read_to_string(board_config_path.as_path())?;
634        let toml = config.parse::<toml_edit::DocumentMut>().expect("invalid board config toml");
635
636        for link_arg in
637            toml.get("link_args").and_then(toml_edit::Item::as_array).into_iter().flatten()
638        {
639            if let Some(option) = link_arg.as_str() {
640                println!("cargo:rustc-link-arg={option}");
641            }
642        }
643
644        for link_search_path in
645            toml.get("link_search_path").and_then(toml_edit::Item::as_array).into_iter().flatten()
646        {
647            if let Some(mut path) = link_search_path.as_str().map(std::path::PathBuf::from) {
648                if path.is_relative() {
649                    path = board_config_path.parent().unwrap().join(path);
650                }
651                println!("cargo:rustc-link-search={}", path.to_string_lossy());
652            }
653        }
654        println!("cargo:rerun-if-env-changed=DEP_MCU_BOARD_SUPPORT_MCU_BOARD_CONFIG_PATH");
655        println!("cargo:rerun-if-changed={}", board_config_path.display());
656    }
657
658    Ok(())
659}
660
661#[cfg(test)]
662fn root_path_prefix() -> std::path::PathBuf {
663    #[cfg(windows)]
664    return std::path::PathBuf::from("C:/");
665    #[cfg(not(windows))]
666    return std::path::PathBuf::from("/");
667}
668
669#[test]
670fn with_absolute_library_paths_test() {
671    use std::path::PathBuf;
672
673    let library_paths = std::collections::HashMap::from([
674        ("relative".to_string(), PathBuf::from("some/relative/path")),
675        ("absolute".to_string(), root_path_prefix().join("some/absolute/path")),
676    ]);
677    let config = CompilerConfiguration::new().with_library_paths(library_paths);
678
679    let manifest_path = root_path_prefix().join("path/to/manifest");
680    let absolute_config = config.clone().with_absolute_paths(&manifest_path);
681    let relative = &absolute_config.config.library_paths["relative"];
682    assert!(relative.is_absolute());
683    assert!(relative.starts_with(&manifest_path));
684
685    assert!(!absolute_config.config.library_paths["absolute"].starts_with(&manifest_path));
686}
687
688#[test]
689fn with_absolute_include_paths_test() {
690    use std::path::PathBuf;
691
692    let config = CompilerConfiguration::new().with_include_paths(Vec::from([
693        root_path_prefix().join("some/absolute/path"),
694        PathBuf::from("some/relative/path"),
695    ]));
696
697    let manifest_path = root_path_prefix().join("path/to/manifest");
698    let absolute_config = config.clone().with_absolute_paths(&manifest_path);
699    assert_eq!(
700        absolute_config.config.include_paths,
701        Vec::from([
702            root_path_prefix().join("some/absolute/path"),
703            manifest_path.join("some/relative/path"),
704        ])
705    )
706}