-
Notifications
You must be signed in to change notification settings - Fork 374
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Codegen: Multi-pass refactoring & support for
--check
(#3918)
- Adds a new `CodeFormatter` trait. - Adds a new trait-based generic `generate_code` helper, and reimplement all language-specific backends in terms of it. - Adds `--check` flag. - Use `--check` instead of git dirtyness checks on CI. --- - Fixes #3495
- Loading branch information
Showing
9 changed files
with
386 additions
and
240 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
use crate::CodeFormatter; | ||
|
||
// --- | ||
|
||
pub struct CppCodeFormatter; | ||
|
||
impl CodeFormatter for CppCodeFormatter { | ||
fn format(&mut self, _reporter: &crate::Reporter, files: &mut crate::GeneratedFiles) { | ||
use rayon::prelude::*; | ||
|
||
re_tracing::profile_function!(); | ||
|
||
files.par_iter_mut().for_each(|(filepath, contents)| { | ||
*contents = if matches!(filepath.extension(), Some("cpp" | "hpp")) { | ||
format_code(contents) | ||
} else { | ||
contents.clone() | ||
}; | ||
}); | ||
} | ||
} | ||
|
||
fn format_code(code: &str) -> String { | ||
clang_format::clang_format_with_style(code, &clang_format::ClangFormatStyle::File) | ||
.expect("Failed to run clang-format") | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
/// Implements the formatting pass. | ||
pub trait CodeFormatter { | ||
/// Formats generated files in-place. | ||
fn format(&mut self, reporter: &crate::Reporter, files: &mut crate::GeneratedFiles); | ||
} | ||
|
||
pub struct NoopCodeFormatter; | ||
|
||
impl CodeFormatter for NoopCodeFormatter { | ||
fn format(&mut self, _reporter: &crate::Reporter, _files: &mut crate::GeneratedFiles) {} | ||
} | ||
|
||
// --- | ||
|
||
mod cpp; | ||
mod python; | ||
mod rust; | ||
|
||
pub use self::cpp::CppCodeFormatter; | ||
pub use self::python::PythonCodeFormatter; | ||
pub use self::rust::RustCodeFormatter; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,145 @@ | ||
use anyhow::Context; | ||
use camino::{Utf8Path, Utf8PathBuf}; | ||
|
||
use crate::CodeFormatter; | ||
|
||
// --- | ||
|
||
pub struct PythonCodeFormatter { | ||
pub pkg_path: Utf8PathBuf, | ||
pub testing_pkg_path: Utf8PathBuf, | ||
} | ||
|
||
impl PythonCodeFormatter { | ||
pub fn new(pkg_path: impl Into<Utf8PathBuf>, testing_pkg_path: impl Into<Utf8PathBuf>) -> Self { | ||
Self { | ||
pkg_path: pkg_path.into(), | ||
testing_pkg_path: testing_pkg_path.into(), | ||
} | ||
} | ||
} | ||
|
||
impl CodeFormatter for PythonCodeFormatter { | ||
fn format(&mut self, _reporter: &crate::Reporter, files: &mut crate::GeneratedFiles) { | ||
use rayon::prelude::*; | ||
|
||
re_tracing::profile_function!(); | ||
|
||
// Running `black` once for each file is very slow, so we write all | ||
// files to a temporary folder, format it, and copy back the results. | ||
|
||
let tempdir = tempfile::tempdir().unwrap(); | ||
let tempdir_path = Utf8PathBuf::try_from(tempdir.path().to_owned()).unwrap(); | ||
|
||
files.par_iter().for_each(|(filepath, contents)| { | ||
let formatted_source_path = format_path_for_tmp_dir( | ||
&self.pkg_path, | ||
&self.testing_pkg_path, | ||
filepath, | ||
&tempdir_path, | ||
); | ||
crate::codegen::common::write_file(&formatted_source_path, contents); | ||
}); | ||
|
||
format_python_dir(&tempdir_path).unwrap(); | ||
|
||
// Read back and copy to the final destination: | ||
files.par_iter_mut().for_each(|(filepath, contents)| { | ||
let formatted_source_path = format_path_for_tmp_dir( | ||
&self.pkg_path, | ||
&self.testing_pkg_path, | ||
filepath, | ||
&tempdir_path, | ||
); | ||
*contents = std::fs::read_to_string(formatted_source_path).unwrap(); | ||
}); | ||
} | ||
} | ||
|
||
fn format_path_for_tmp_dir( | ||
pkg_path: &Utf8Path, | ||
testing_pkg_path: &Utf8Path, | ||
filepath: &Utf8Path, | ||
tempdir_path: &Utf8Path, | ||
) -> Utf8PathBuf { | ||
// If the prefix is pkg_path, strip it, and then append to tempdir | ||
// However, if the prefix is testing_pkg_path, strip it and insert an extra | ||
// "testing" to avoid name collisions. | ||
filepath.strip_prefix(pkg_path).map_or_else( | ||
|_| { | ||
tempdir_path | ||
.join("testing") | ||
.join(filepath.strip_prefix(testing_pkg_path).unwrap()) | ||
}, | ||
|f| tempdir_path.join(f), | ||
) | ||
} | ||
|
||
fn format_python_dir(dir: &Utf8PathBuf) -> anyhow::Result<()> { | ||
re_tracing::profile_function!(); | ||
|
||
// The order below is important and sadly we need to call black twice. Ruff does not yet | ||
// fix line-length (See: https://github.com/astral-sh/ruff/issues/1904). | ||
// | ||
// 1) Call black, which among others things fixes line-length | ||
// 2) Call ruff, which requires line-lengths to be correct | ||
// 3) Call black again to cleanup some whitespace issues ruff might introduce | ||
|
||
run_black_on_dir(dir).context("black")?; | ||
run_ruff_on_dir(dir).context("ruff")?; | ||
run_black_on_dir(dir).context("black")?; | ||
Ok(()) | ||
} | ||
|
||
fn python_project_path() -> Utf8PathBuf { | ||
let path = crate::rerun_workspace_path() | ||
.join("rerun_py") | ||
.join("pyproject.toml"); | ||
assert!(path.exists(), "Failed to find {path:?}"); | ||
path | ||
} | ||
|
||
fn run_black_on_dir(dir: &Utf8PathBuf) -> anyhow::Result<()> { | ||
re_tracing::profile_function!(); | ||
use std::process::{Command, Stdio}; | ||
|
||
let proc = Command::new("black") | ||
.arg(format!("--config={}", python_project_path())) | ||
.arg(dir) | ||
.stdout(Stdio::piped()) | ||
.stderr(Stdio::piped()) | ||
.spawn()?; | ||
|
||
let output = proc.wait_with_output()?; | ||
|
||
if output.status.success() { | ||
Ok(()) | ||
} else { | ||
let stdout = String::from_utf8_lossy(&output.stdout).trim().to_owned(); | ||
let stderr = String::from_utf8_lossy(&output.stderr).trim().to_owned(); | ||
anyhow::bail!("{stdout}\n{stderr}") | ||
} | ||
} | ||
|
||
fn run_ruff_on_dir(dir: &Utf8PathBuf) -> anyhow::Result<()> { | ||
re_tracing::profile_function!(); | ||
use std::process::{Command, Stdio}; | ||
|
||
let proc = Command::new("ruff") | ||
.arg(format!("--config={}", python_project_path())) | ||
.arg("--fix") | ||
.arg(dir) | ||
.stdout(Stdio::piped()) | ||
.stderr(Stdio::piped()) | ||
.spawn()?; | ||
|
||
let output = proc.wait_with_output()?; | ||
|
||
if output.status.success() { | ||
Ok(()) | ||
} else { | ||
let stdout = String::from_utf8_lossy(&output.stdout).trim().to_owned(); | ||
let stderr = String::from_utf8_lossy(&output.stderr).trim().to_owned(); | ||
anyhow::bail!("{stdout}\n{stderr}") | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
use crate::CodeFormatter; | ||
|
||
// --- | ||
|
||
pub struct RustCodeFormatter; | ||
|
||
impl CodeFormatter for RustCodeFormatter { | ||
fn format(&mut self, _reporter: &crate::Reporter, files: &mut crate::GeneratedFiles) { | ||
use rayon::prelude::*; | ||
|
||
re_tracing::profile_function!(); | ||
|
||
files.par_iter_mut().for_each(|(filepath, contents)| { | ||
*contents = if matches!(filepath.extension(), Some("rs")) { | ||
format_code(contents) | ||
} else { | ||
contents.clone() | ||
}; | ||
}); | ||
} | ||
} | ||
|
||
fn format_code(contents: &str) -> String { | ||
re_tracing::profile_function!(); | ||
|
||
let mut contents = contents.replace(" :: ", "::"); // Fix `bytemuck :: Pod` -> `bytemuck::Pod`. | ||
|
||
// Even though we already have used `prettyplease` we also | ||
// need to run `cargo fmt`, since it catches some things `prettyplease` missed. | ||
// We need to run `cago fmt` several times because it is not idempotent; | ||
// see https://github.com/rust-lang/rustfmt/issues/5824 | ||
for _ in 0..2 { | ||
// NOTE: We're purposefully ignoring the error here. | ||
// | ||
// In the very unlikely chance that the user doesn't have the `fmt` component installed, | ||
// there's still no good reason to fail the build. | ||
// | ||
// The CI will catch the unformatted file at PR time and complain appropriately anyhow. | ||
|
||
re_tracing::profile_scope!("rust-fmt"); | ||
use rust_format::Formatter as _; | ||
if let Ok(formatted) = rust_format::RustFmt::default().format_str(&contents) { | ||
contents = formatted; | ||
} | ||
} | ||
|
||
contents | ||
} |
Oops, something went wrong.