Skip to content

Add comprehensive linting #1327

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 25 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions .github/workflows/github-cxx-qt-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@ jobs:
# cargo install --locked --no-default-features --version 0.9.1 sccache
run: |
cargo install --locked --no-default-features --git=https://github.com/ahayzen-kdab/sccache --branch=2092-ignore-notfound-errors sccache
cargo install --locked --version 0.4.36 mdbook
cargo install --locked --version 0.4.36 mdbook
cargo install --locked --version 0.7.7 mdbook-linkcheck
# We want our compiler cache to always update to the newest state.
# The best way for us to achieve this is to **always** update the cache after every landed commit.
Expand Down Expand Up @@ -203,7 +203,7 @@ jobs:
./emsdk/emsdk install 2.0.14
./emsdk/emsdk activate 2.0.14
- name: "Install Dependencies"
run: |
run: |
sudo apt-get update && sudo apt-get install -y ninja-build
pip install --user --break-system-packages clang-format==18.1.8
test -x ~/.local/bin/clang-format
Expand Down Expand Up @@ -426,12 +426,12 @@ jobs:
- name: "Clone Git repository"
uses: actions/checkout@v5
# Ensure clippy and rustfmt is installed, they should come from github runner
# clippy version needs to be 1.78.0 for the MSRV lint
# clippy version needs to be 1.87.0 for all lints
#
# Note we still need rustfmt for the cxx-qt-gen tests
- name: "Install Rust toolchain"
run: |
rustup toolchain add 1.78.0 --component clippy
rustup toolchain add 1.87.0 --component clippy
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

before we were testing in CI with our MSRV, do we not want to do this now?

Or instead should something like clippy be in it's own runner using a newer Rust toolchain 🤔

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMO, clippy could also just be separate runner, as I think it doesn't share many build artifacts with the main build anyhow.

rustup default 1.77.2
rustup component add rustfmt

Expand All @@ -450,7 +450,7 @@ jobs:
# cargo install --locked --no-default-features --version 0.9.1 sccache
run: |
cargo install --locked --no-default-features --git=https://github.com/ahayzen-kdab/sccache --branch=2092-ignore-notfound-errors sccache
cargo install --locked --version 0.4.36 mdbook
cargo install --locked --version 0.4.36 mdbook
cargo install --locked --version 0.7.7 mdbook-linkcheck

# We want our compiler cache to always update to the newest state.
Expand Down
12 changes: 6 additions & 6 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ if(NOT USE_QT5)
endif()
if(NOT Qt6_FOUND)
if(BUILD_WASM)
message(FATAL_ERROR
message(FATAL_ERROR
"CXX-Qt for WebAssembly only currently supports Qt 6 builds."
)
endif()
Expand Down Expand Up @@ -151,25 +151,25 @@ if(BUILD_TESTING)
# Add CMake tests for `cargo test/clippy/fmt/doc`.
add_test(NAME cargo_tests COMMAND cargo test --locked --release --all-features --target-dir ${CARGO_TARGET_DIR})
add_test(NAME cargo_doc COMMAND cargo doc --locked --release --all-features --target-dir ${CARGO_TARGET_DIR})
# Minimum clippy version for the incompatible_msrv lint is 1.78.0
add_test(NAME cargo_clippy COMMAND cargo +1.78.0 clippy --locked --release --all-features --target-dir ${CARGO_TARGET_DIR} -- -D warnings)
# Minimum clippy version for all lints is 1.87.0
add_test(NAME cargo_clippy COMMAND cargo +1.87.0 clippy --locked --release --all-features --target-dir ${CARGO_TARGET_DIR} -- -D warnings)

set_tests_properties(cargo_tests cargo_clippy PROPERTIES
ENVIRONMENT_MODIFICATION "${CARGO_ENV}"
)
set_tests_properties(cargo_doc PROPERTIES
ENVIRONMENT_MODIFICATION "${CARGO_ENV};RUSTDOCFLAGS=set:--deny=warnings"
)

# Ensure test inputs and outputs are formatted
file(GLOB CXX_QT_GEN_TEST_INPUTS ${CMAKE_CURRENT_SOURCE_DIR}/crates/cxx-qt-gen/test_inputs/*.rs)
file(GLOB CXX_QT_GEN_TEST_OUTPUTS ${CMAKE_CURRENT_SOURCE_DIR}/crates/cxx-qt-gen/test_outputs/*.rs)
add_test(NAME cxx_qt_gen_test_inputs_gen COMMAND rustfmt --check ${CXX_QT_GEN_TEST_INPUTS})
add_test(NAME cxx_qt_gen_test_outputs_gen COMMAND rustfmt --check ${CXX_QT_GEN_TEST_OUTPUTS})

# Add test which checks that a build rerun doesn't recompile and uses caches instead
add_test(NAME cargo_build_rerun COMMAND "${CMAKE_CURRENT_SOURCE_DIR}/scripts/check_cargo_build_rerun.sh" "${CMAKE_CURRENT_SOURCE_DIR}" "${CARGO_TARGET_DIR}")

# Ensure that cargo_build_rerun doesn't run while we are already building
set_tests_properties(cargo_build_rerun PROPERTIES RUN_SERIAL TRUE)
endif()
Expand Down
26 changes: 22 additions & 4 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -65,8 +65,26 @@ serde_json = "1.0"
thiserror = "1.0"

[workspace.lints.clippy]
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i guess my question would be why are these lints not enabled by default? Do we want to enable so many? I usually follow the defaults for lints as there is sometimes a reason why they are not enabled yet. As otherwise this can become an explosion of debates.

Copy link
Contributor Author

@jnbooth jnbooth Aug 20, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No clippy lints are enabled by default as far as I'm aware. The nice thing about making lints opt-out instead of opt-in is that there are a lot of lints and I think most of them are useful—I learned some new best practices in the course of making this PR, such as clippy::ptr_as_ptr. This PR has actually been something I've been working on for a while because it has been a useful exercise for me, and it has yielded other PRs in the process such as #1324 and #1321. So the effort was worthwhile in itself, even if this PR doesn't get merged.

all = { level = "warn", priority = -1 }
pedantic = { level = "warn", priority = -1 }
cast_possible_truncation = "allow"
cast_possible_wrap = "allow"
cast_sign_loss = "allow"
doc_link_with_quotes = "allow"
doc_markdown = "allow"
if_not_else = "allow"
incompatible_msrv = "deny"
if_not_else = "warn"
manual_let_else = "warn"
redundant_else = "warn"
single_match_else = "warn"
inline_always = "allow"
manual_let_else = "allow"
many_single_char_names = "allow"
map_unwrap_or = "allow"
match_wildcard_for_single_variants = "allow"
module_name_repetitions = "allow"
must_use_candidate = "allow"
redundant_else = "allow"
return_self_not_must_use = "allow"
should_panic_without_expect = "allow"
similar_names = "allow"
single_match_else = "allow"
struct_excessive_bools = "allow"
too_many_lines = "allow"
3 changes: 2 additions & 1 deletion crates/cxx-qt-build/src/cfg_evaluator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ use std::borrow::Borrow;
use std::cmp::Ordering;
use std::collections::{BTreeMap as Map, BTreeSet as Set};
use std::env;
use std::ptr;
use std::sync::OnceLock;

static ENV: OnceLock<CargoEnv> = OnceLock::new();
Expand Down Expand Up @@ -102,7 +103,7 @@ struct Lookup(str);

impl Lookup {
fn new(name: &str) -> &Self {
unsafe { &*(name as *const str as *const Self) }
unsafe { &*(ptr::from_ref(name) as *const Self) }
}
}

Expand Down
8 changes: 5 additions & 3 deletions crates/cxx-qt-build/src/diagnostics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -159,9 +159,11 @@ impl Diagnostic {
pub(crate) fn report(self) {
// If loading the source file fails, or printing to stderr isn't
// possible, we try panicing as a last resort.
self.try_report().unwrap_or_else(|_| {
panic!("{}", self.errors.first().unwrap());
})
assert!(
self.try_report().is_ok(),
"{}",
self.errors.first().unwrap()
);
}
}

Expand Down
7 changes: 4 additions & 3 deletions crates/cxx-qt-build/src/interface.rs
Original file line number Diff line number Diff line change
Expand Up @@ -81,9 +81,10 @@ impl Interface {
}

// Ensure that a link name has been set
if self.manifest.link_name.is_empty() {
panic!("The links key must be set when exporting with CXX-Qt-build");
}
assert!(
!self.manifest.link_name.is_empty(),
"The links key must be set when exporting with CXX-Qt-build"
);

// We automatically reexport all qt_modules and downstream dependencies
// as they will always need to be enabled in the final binary.
Expand Down
67 changes: 28 additions & 39 deletions crates/cxx-qt-build/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@
// SPDX-FileContributor: Gerhard de Clercq <[email protected]>
//
// SPDX-License-Identifier: MIT OR Apache-2.0

#![deny(missing_docs)]
#![allow(clippy::missing_panics_doc)]

//! This crate provides a builder which parses given Rust source code to search
//! for CXX-Qt or CXX macros and generate any resulting C++ code. It also builds
Expand Down Expand Up @@ -115,24 +115,20 @@ impl GeneratedCpp {
match item {
CxxQtItem::Cxx(m) => {
// TODO: later we will allow for multiple CXX or CXX-Qt blocks in one file
if found_bridge {
panic!(
assert!(!found_bridge,
"Unfortunately only files with either a single cxx or a single cxx_qt module are currently supported.
The file {} has more than one of these.",
rust_file_path.display());
}
found_bridge = true;

tokens.extend(m.into_token_stream());
}
CxxQtItem::CxxQt(m) => {
// TODO: later we will allow for multiple CXX or CXX-Qt blocks in one file
if found_bridge {
panic!(
assert!(!found_bridge,
"Unfortunately only files with either a single cxx or a single cxx_qt module are currently supported.
The file {} has more than one of these.",
rust_file_path.display());
}
found_bridge = true;

let mut parser = Parser::from(*m.clone())
Expand Down Expand Up @@ -203,8 +199,7 @@ impl GeneratedCpp {
let mut header =
File::create(&header_path).expect("Could not create cxx-qt header file");
let header_generated = match cxx_qt_generated {
CppFragment::Pair { header, source: _ } => header,
CppFragment::Header(header) => header,
CppFragment::Header(header) | CppFragment::Pair { header, source: _ } => header,
CppFragment::Source(_) => panic!("Unexpected call for source fragment."),
};
header
Expand All @@ -223,9 +218,8 @@ impl GeneratedCpp {
}
let mut cpp = File::create(&cpp_path).expect("Could not create cxx-qt source file");
let source_generated = match cxx_qt_generated {
CppFragment::Pair { header: _, source } => source,
CppFragment::Source(source) | CppFragment::Pair { header: _, source } => source,
CppFragment::Header(_) => panic!("Unexpected call for header fragment."),
CppFragment::Source(source) => source,
};
cpp.write_all(source_generated.as_bytes())
.expect("Could not write cxx-qt source file");
Expand Down Expand Up @@ -521,11 +515,9 @@ impl CxxQtBuilder {
/// to disable any qt modules that are optional.
pub fn qt_module(mut self, module: &str) -> Self {
// Ensure that CMake and Cargo build.rs are not out of sync
if qt_modules_import().is_some() && !self.qt_modules.contains(module) {
panic!("Qt module mismatch between cxx-qt-build and CMake!\n\
assert!(qt_modules_import().is_none() || self.qt_modules.contains(module), "Qt module mismatch between cxx-qt-build and CMake!\n\
Qt module '{module}' was not specified in CMake!\n\
When building with CMake, all Qt modules must be specified with the QT_MODULES argument in cxx_qt_import_crate");
}

self.qt_modules.insert(module.to_owned());
self
Expand Down Expand Up @@ -608,7 +600,7 @@ impl CxxQtBuilder {
self
}

fn define_cfg_variable(key: String, value: Option<&str>) {
fn define_cfg_variable(key: &str, value: Option<&str>) {
if let Some(value) = value {
println!("cargo::rustc-cfg={key}=\"{value}\"");
} else {
Expand All @@ -618,7 +610,7 @@ impl CxxQtBuilder {
env::set_var(variable_cargo, value.unwrap_or("true"));
}

fn define_cfg_check_variable(key: String, values: Option<Vec<&str>>) {
fn define_cfg_check_variable(key: &str, values: Option<Vec<&str>>) {
if let Some(values) = values {
let values = values
.iter()
Expand All @@ -632,29 +624,26 @@ impl CxxQtBuilder {
}
}

fn define_qt_version_cfg_variables(version: Version) {
fn define_qt_version_cfg_variables(version: &Version) {
// Allow for Qt 5 or Qt 6 as valid values
CxxQtBuilder::define_cfg_check_variable(
"cxxqt_qt_version_major".to_owned(),
Some(vec!["5", "6"]),
);
CxxQtBuilder::define_cfg_check_variable("cxxqt_qt_version_major", Some(vec!["5", "6"]));
// Find the Qt version and tell the Rust compiler
// this allows us to have conditional Rust code
CxxQtBuilder::define_cfg_variable(
"cxxqt_qt_version_major".to_owned(),
"cxxqt_qt_version_major",
Some(version.major.to_string().as_str()),
);

// Seed all values from Qt 5.0 through to Qt 7.99
for major in 5..=7 {
CxxQtBuilder::define_cfg_check_variable(
format!("cxxqt_qt_version_at_least_{major}"),
&format!("cxxqt_qt_version_at_least_{major}"),
None,
);

for minor in 0..=99 {
CxxQtBuilder::define_cfg_check_variable(
format!("cxxqt_qt_version_at_least_{major}_{minor}"),
&format!("cxxqt_qt_version_at_least_{major}_{minor}"),
None,
);
}
Expand All @@ -663,13 +652,13 @@ impl CxxQtBuilder {
for minor in 0..=version.minor {
let qt_version_at_least =
format!("cxxqt_qt_version_at_least_{}_{}", version.major, minor);
CxxQtBuilder::define_cfg_variable(qt_version_at_least, None);
CxxQtBuilder::define_cfg_variable(&qt_version_at_least, None);
}

// We don't support Qt < 5
for major in 5..=version.major {
let at_least_qt_major_version = format!("cxxqt_qt_version_at_least_{major}");
CxxQtBuilder::define_cfg_variable(at_least_qt_major_version, None);
CxxQtBuilder::define_cfg_variable(&at_least_qt_major_version, None);
}
}

Expand All @@ -689,7 +678,7 @@ impl CxxQtBuilder {
// A dependency can specify which of its own include paths it wants to export.
// Set up each of these exported include paths as symlinks in our own include directory,
// or deep copy the files if the platform does not support symlinks.
fn include_dependency(&mut self, dependency: &Dependency) {
fn include_dependency(dependency: &Dependency) {
let header_root = dir::header_root();
let dependency_root = dependency.path.join("include");
for include_prefix in &dependency.manifest.exported_include_prefixes {
Expand Down Expand Up @@ -760,9 +749,9 @@ impl CxxQtBuilder {
fn export_object_file(
mut obj_builder: cc::Build,
file_path: impl AsRef<Path>,
export_path: PathBuf,
export_path: &Path,
) {
obj_builder.file(file_path.as_ref());
obj_builder.file(file_path);

// We only expect a single file, so destructure the vec.
// If there's 0 or > 1 file, we panic in the `else` branch, because then the builder is
Expand All @@ -777,7 +766,7 @@ impl CxxQtBuilder {
)
});
}
std::fs::copy(obj_file, &export_path).unwrap_or_else(|_| {
std::fs::copy(obj_file, export_path).unwrap_or_else(|_| {
panic!("Failed to export object file to {}!", export_path.display())
});
} else {
Expand Down Expand Up @@ -825,13 +814,12 @@ impl CxxQtBuilder {
}
})
.collect::<HashSet<String>>();
if dirs.len() > 1 {
panic!(
"Only one directory is supported per QmlModule for rust_files.\n\
assert!(
dirs.len() <= 1,
"Only one directory is supported per QmlModule for rust_files.\n\
This is due to Qt bug https://bugreports.qt.io/browse/QTBUG-93443\n\
Found directories: {dirs:?}"
);
}
);

// TODO: for now we use the global CxxQtBuilder cc_builder
// this means that any includes/files etc on these are in this builder
Expand Down Expand Up @@ -1035,7 +1023,7 @@ extern "C" bool {init_fun}() {{
std::fs::write(&init_file, init_call).expect("Could not write initializers call file!");

if let Some(export_path) = export_path {
Self::export_object_file(init_call_builder, init_file, export_path);
Self::export_object_file(init_call_builder, init_file, &export_path);
} else {
// Link the call-init-lib with +whole-archive to ensure that the static initializers are not discarded.
// We previously used object files that we linked directly into the final binary, but this caused
Expand Down Expand Up @@ -1082,6 +1070,8 @@ extern "C" bool {init_fun}() {{
/// Generate and compile cxx-qt C++ code, as well as compile any additional files from
/// [CxxQtBuilder::qobject_header] and [CxxQtBuilder::cc_builder].
pub fn build(mut self) -> Interface {
const MAX_INCLUDE_DEPTH: usize = 6;

dir::clean(dir::crate_target()).expect("Failed to clean crate export directory!");

// We will do these two steps first, as setting up the dependencies can modify flags we
Expand All @@ -1091,7 +1081,7 @@ extern "C" bool {init_fun}() {{
Self::write_common_headers();
let dependencies = Dependency::find_all();
for dependency in &dependencies {
self.include_dependency(dependency);
Self::include_dependency(dependency);
}
let qt_modules = self.qt_modules(&dependencies);

Expand All @@ -1103,7 +1093,7 @@ extern "C" bool {init_fun}() {{
let mut qtbuild = qt_build_utils::QtBuild::new(qt_modules.iter().cloned().collect())
.expect("Could not find Qt installation");
qtbuild.cargo_link_libraries(&mut self.cc_builder);
Self::define_qt_version_cfg_variables(qtbuild.version());
Self::define_qt_version_cfg_variables(&qtbuild.version());

// Ensure that Qt modules and apple framework are linked and searched correctly
let mut include_paths = qtbuild.include_paths();
Expand All @@ -1115,7 +1105,6 @@ extern "C" bool {init_fun}() {{
// to the generated files without any namespacing.
include_paths.push(header_root.join(&self.include_prefix));

const MAX_INCLUDE_DEPTH: usize = 6;
let crate_header_dir = self.crate_include_root.as_ref().map(|subdir| {
dir::manifest()
.expect("Could not find crate directory!")
Expand Down
Loading
Loading