qt_build_utils/tool/
rcc.rs

1// SPDX-FileCopyrightText: 2025 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.com>
2// SPDX-FileContributor: Andrew Hayzen <andrew.hayzen@kdab.com>
3//
4// SPDX-License-Identifier: MIT OR Apache-2.0
5
6use crate::{Initializer, QtInstallation, QtTool};
7
8use semver::Version;
9use std::{
10    ffi::{OsStr, OsString},
11    path::{Path, PathBuf},
12    process::Command,
13};
14
15/// A wrapper around the [rcc](https://doc.qt.io/qt-6/resources.html) tool
16pub struct QtToolRcc {
17    executable: PathBuf,
18    qt_version: Version,
19    custom_args: Vec<OsString>,
20}
21
22impl QtToolRcc {
23    /// Construct a [QtToolRcc] from a given [QtInstallation]
24    pub fn new(qt_installation: &dyn QtInstallation) -> Self {
25        let executable = qt_installation
26            .try_find_tool(QtTool::Rcc)
27            .expect("Could not find rcc");
28        let qt_version = qt_installation.version();
29
30        Self {
31            executable,
32            qt_version,
33            custom_args: Vec::new(),
34        }
35    }
36
37    /// Append custom arguments to the end of the rcc invocation.
38    pub fn custom_args(mut self, args: impl IntoIterator<Item = impl AsRef<OsStr>>) -> Self {
39        self.custom_args = args
40            .into_iter()
41            .map(|s| s.as_ref().to_os_string())
42            .collect();
43        self
44    }
45
46    /// Run [rcc](https://doc.qt.io/qt-6/resources.html) on a .qrc file and save the output into [cargo's OUT_DIR](https://doc.rust-lang.org/cargo/reference/environment-variables.html).
47    /// The path to the generated C++ file is returned, which can then be passed to [cc::Build::files](https://docs.rs/cc/latest/cc/struct.Build.html#method.file).
48    /// This function also returns a String that contains the name of the resource initializer
49    /// function.
50    /// The build system must ensure that if the .cpp file is built into a static library, either
51    /// the `+whole-archive` flag is used, or the initializer function is called by the
52    /// application.
53    pub fn compile(&self, input_file: impl AsRef<Path>) -> Initializer {
54        let input_path = input_file.as_ref();
55        let output_folder = QtTool::Rcc.writable_path();
56        std::fs::create_dir_all(&output_folder).expect("Could not create qrc dir");
57        let mut output_path = output_folder.join(input_path.file_name().unwrap());
58        output_path.set_extension("cpp");
59        let name = input_path
60            .file_name()
61            .unwrap()
62            .to_string_lossy()
63            .replace('.', "_");
64
65        let mut args = vec![
66            OsString::from(input_path),
67            OsString::from("-o"),
68            OsString::from(&output_path),
69            OsString::from("--name"),
70            OsString::from(&name),
71        ];
72        args.extend(self.custom_args.iter().cloned());
73
74        let cmd = Command::new(&self.executable)
75            .args(args)
76            .output()
77            .unwrap_or_else(|_| panic!("rcc failed for {}", input_path.display()));
78
79        if !cmd.status.success() {
80            panic!(
81                "rcc failed for {}:\n{}",
82                input_path.display(),
83                String::from_utf8_lossy(&cmd.stderr)
84            );
85        }
86
87        let qt_6_5 = Version::new(6, 5, 0);
88        let init_header = if self.qt_version >= qt_6_5 {
89            // With Qt6.5 the Q_INIT_RESOURCE macro is in the QtResource header
90            "QtCore/QtResource"
91        } else {
92            "QtCore/QDir"
93        };
94        Initializer {
95            file: Some(output_path),
96            init_call: Some(format!("Q_INIT_RESOURCE({name});")),
97            init_declaration: Some(format!("#include <{init_header}>")),
98        }
99    }
100
101    /// Run [rcc](https://doc.qt.io/qt-6/resources.html) on a .qrc file and return the paths of the sources
102    pub fn list(&self, input_file: impl AsRef<Path>) -> Vec<PathBuf> {
103        // Add the qrc file contents to the cargo rerun list
104        let input_path = input_file.as_ref();
105        let cmd_list = Command::new(&self.executable)
106            .args(["--list", input_path.to_str().unwrap()])
107            .output()
108            .unwrap_or_else(|_| panic!("rcc --list failed for {}", input_path.display()));
109
110        if !cmd_list.status.success() {
111            panic!(
112                "rcc --list failed for {}:\n{}",
113                input_path.display(),
114                String::from_utf8_lossy(&cmd_list.stderr)
115            );
116        }
117
118        String::from_utf8_lossy(&cmd_list.stdout)
119            .split('\n')
120            .map(PathBuf::from)
121            .collect()
122    }
123}