qt_build_utils/tool/
moc.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::{QmlUri, QtInstallation, QtTool};
7
8use std::{
9    path::{Path, PathBuf},
10    process::Command,
11};
12
13/// Paths to files generated by [QtToolMoc::compile]
14pub struct MocProducts {
15    /// Generated C++ file
16    pub cpp: PathBuf,
17    /// Generated JSON file
18    pub metatypes_json: PathBuf,
19}
20
21/// Arguments for a Qt moc invocation.
22/// See: [QtToolMoc::compile]
23#[derive(Default, Clone)]
24pub struct MocArguments {
25    uri: Option<QmlUri>,
26    include_paths: Vec<PathBuf>,
27}
28
29impl MocArguments {
30    /// Should be passed if the input_file is part of a QML module
31    pub fn uri(mut self, uri: impl Into<QmlUri>) -> Self {
32        self.uri = Some(uri.into());
33        self
34    }
35
36    /// Returns the assigned URI, if any.
37    pub fn get_uri(&self) -> Option<&QmlUri> {
38        self.uri.as_ref()
39    }
40
41    /// Additional include path to pass to moc
42    pub fn include_path(mut self, include_path: impl AsRef<Path>) -> Self {
43        self.include_paths.push(include_path.as_ref().to_owned());
44        self
45    }
46
47    /// Additional include paths to pass to moc.
48    pub fn include_paths(
49        mut self,
50        include_paths: impl IntoIterator<Item = impl AsRef<Path>>,
51    ) -> Self {
52        self.include_paths.extend(
53            include_paths
54                .into_iter()
55                .map(|path| path.as_ref().to_owned()),
56        );
57        self
58    }
59}
60
61/// A wrapper around the [moc](https://doc.qt.io/qt-6/moc.html) tool
62pub struct QtToolMoc {
63    executable: PathBuf,
64    qt_include_paths: Vec<PathBuf>,
65    qt_framework_paths: Vec<PathBuf>,
66}
67
68impl QtToolMoc {
69    /// Construct a [QtToolMoc] from a given [QtInstallation]
70    pub fn new(qt_installation: &dyn QtInstallation, qt_modules: &[String]) -> Self {
71        let executable = qt_installation
72            .try_find_tool(QtTool::Moc)
73            .expect("Could not find moc");
74        let qt_include_paths = qt_installation.include_paths(qt_modules);
75        let qt_framework_paths = qt_installation.framework_paths(qt_modules);
76
77        Self {
78            executable,
79            qt_include_paths,
80            qt_framework_paths,
81        }
82    }
83
84    /// Run moc on a C++ header file and save the output into [cargo's OUT_DIR](https://doc.rust-lang.org/cargo/reference/environment-variables.html).
85    /// The return value contains the path to the generated C++ file, which can then be passed to [cc::Build::files](https://docs.rs/cc/latest/cc/struct.Build.html#method.file),
86    /// as well as the path to the generated metatypes.json file, which can be used for QML modules.
87    pub fn compile(&self, input_file: impl AsRef<Path>, arguments: MocArguments) -> MocProducts {
88        let input_path = input_file.as_ref();
89        // Put all the moc files into one place, this can then be added to the include path
90        let moc_dir = QtTool::Moc.writable_path();
91        std::fs::create_dir_all(&moc_dir).expect("Could not create moc dir");
92        let output_path = moc_dir.join(format!(
93            "moc_{}.cpp",
94            input_path.file_name().unwrap().to_str().unwrap()
95        ));
96
97        let metatypes_json_path = PathBuf::from(&format!("{}.json", output_path.display()));
98
99        let mut include_args = vec![];
100        // Qt includes
101        for include_path in self
102            .qt_include_paths
103            .iter()
104            .chain(arguments.include_paths.iter())
105        {
106            include_args.push(format!("-I{}", include_path.display()));
107        }
108
109        // Qt frameworks for macOS
110        for framework_path in &self.qt_framework_paths {
111            include_args.push(format!("-F{}", framework_path.display()));
112        }
113
114        let mut cmd = Command::new(&self.executable);
115
116        if let Some(uri) = arguments.uri {
117            cmd.arg(format!("-Muri={uri}", uri = uri.as_dots()));
118        }
119
120        cmd.args(include_args);
121        cmd.arg(input_path.to_str().unwrap())
122            .arg("-o")
123            .arg(output_path.to_str().unwrap())
124            .arg("--output-json");
125        let cmd = cmd
126            .output()
127            .unwrap_or_else(|_| panic!("moc failed for {}", input_path.display()));
128
129        if !cmd.status.success() {
130            panic!(
131                "moc failed for {}:\n{}",
132                input_path.display(),
133                String::from_utf8_lossy(&cmd.stderr)
134            );
135        }
136
137        MocProducts {
138            cpp: output_path,
139            metatypes_json: metatypes_json_path,
140        }
141    }
142}